▄︻┻┳═一「static」Agenda:

▄︻┻┳═一static,你还敢用吗?

▄︻┻┳═一static,你还敢用吗?(二)

▄︻┻┳═一【轻松一刻!】一段难倒了两名老程序猿的简单代码


我用火狐的HttpRequester测试开发组里一个同学发布的Web API接口,遇到了一个奇怪的问题。 我测试边界情况时,第一次调用响应的结果是正常的,但当再次及以后的请求时,却返回了异常“System.ObjectDisposedException: 无法访问已释放的对象”。 每次重新发布后,都是第一次请求是正常的,之后的请求就出现这个异常。

  1. System.ObjectDisposedException: 无法访问已释放的对象。
  2. 对象名:“System.Net.Http.StringContent”。
  3. System.Web.Http.WebHost.HttpControllerHandler.EndProcessRequest(IAsyncResult result)
  4. System.Web.Http.WebHost.HttpControllerHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
  5. System.Web.HttpApplication.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar)

下面是第一次的响应正常的请求。

如下是raw transaction:

POST http://localhost:3102/api/Notification/SmS
Content-Type: application/json
{
"OrderNo": "string",的
"BusinessType": "string",
"SendNodeCode": "string"
}
-- response --
500 Internal Server Error
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 70
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcU291cmNlUHJvamVjdFxub3RpZmljYXRpb25cdHJ1bmtcWU0uTm90aWZpY2F0aW9uXGFwaVxOb3RpZmljYXRpb25cU21T?=
X-Powered-By: ASP.NET
Date: Mon, 06 Jun 2016 11:23:09 GMT

  1.  

{ "data" : null , "status" : "103" , "message" : "数据格式不附"}

  1.  

因为这个API应用程序的日志记录Filter、异常处理Filter的实现和使用方式与另一个API项目很类似。而那个API站点没有出现这样的问题,所以,很奇怪为什么这里出现了这样的异常。

如下是排障过程:

通过调试程序,发现错误定位在如下代码的红色加粗的方法上。 这块代码是用来实现记录统一的接口请求/响应日志的。

  1. public class CustomMessageHandler : DelegatingHandler
  2. {
  3. protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
  4. {
  5. if (!request.RequestUri.ToString().ToLower().Contains("swagger"))
  6. {
  7. SysLogToFile.WriteNotificationMessage(string.Format("请求url:{0}\r\n请求参数:{1}\r\n",
  8. request.RequestUri, request.Content.ReadAsStringAsync().Result));
  9. }
  10. return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>(task =>
  11. {
  12. if (!request.RequestUri.ToString().ToLower().Contains("swagger"))
  13. {
  14. SysLogToFile.WriteNotificationMessage(string.Format("请求url:{0}\r\n请求参数:{1}\r\n响应结果:{2}",
  15. request.RequestUri, request.Content.ReadAsStringAsync().Result, task.Result.Content.ReadAsStringAsync().Result));
  16. }
  17. return task.Result;
  18. });
  19. }
  20. }

其中, task的Result是一个HttpResponseMessage对象,Content是它的一个System.Net.Http.HttpContent类型的属性,如下是老盖茨对其ReadAsStringAsync方法的说明

  1. #region 程序集 System.Net.Http.dll, v2.0.0.0
  2. // D:\SourceProject\notification\trunk\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll
  3. #endregion
  4.  
  5. using System;
  6. using System.IO;
  7. using System.Net;
  8. using System.Net.Http.Headers;
  9. using System.Text;
  10. using System.Threading.Tasks;
  11.  
  12. namespace System.Net.Http
  13. {
  14. // 摘要:
  15. // 表示 HTTP 实体正文和内容标头的基类。
  16. public abstract class HttpContent : IDisposable
  17. {
  18. // 摘要:
  19. // 初始化 System.Net.Http.HttpContent 类的新实例。
  20. protected HttpContent();
  21. //
  22. // 摘要:
  23. // 释放由 System.Net.Http.HttpContent 使用的非托管资源和托管资源。
  24. public void Dispose();
  25. //
  26. // 摘要:
  27. // 释放由 System.Net.Http.HttpContent 使用的非托管资源,并可根据需要释放托管资源。
  28. //
  29. // 参数:
  30. // disposing:
  31. // 如果为 true,则释放托管资源和非托管资源;如果为 false,则仅释放非托管资源。
  32. protected virtual void Dispose(bool disposing);
  33. //
  34. // 摘要:
  35. // 以异步操作将 HTTP 内容写入流。
  36. //
  37. // 返回结果:
  38. // 返回 System.Threading.Tasks.Task<TResult>。 表示异步操作的任务对象。
  39. public Task<string> ReadAsStringAsync();
  40. }
  41. }

通过查看这个CustomMessageHandler类中引用的日志工具类SysLogToFile, 发现写日志文件是通过一个Queue来异步实现的。 那怀疑,是不是在异步记日志时task.Result.Content已经被销毁了呢。 所以,对上面这段记日志的代码做个改动:

  1. string log = string.Format("请求url:{0}\r\n请求参数:{1}\r\n响应结果:{2}",
  2. request.RequestUri, request.Content.ReadAsStringAsync().Result, task.Result.Content.ReadAsStringAsync().Result);
  3. SysLogToFile.WriteNotificationMessage(log);

自信满满地以为这样就没问题了。经再测试后,发现并非如此,问题依然存在。(ps:读书少不行呀,其实.net在调用日志工具类之前,已经把string.Format这段代码给执行并计算了,所以,事先声明个变量是于事无补的)

继续排障,接下来看Sms接口的实现代码。

  1. /// <summary>
  2. /// 通知发短信
  3. /// </summary>
  4. [HttpPost]
  5. public HttpResponseMessage SmS(InPutNotifiSmsModel inputModel)
  6. {
  7. if (inputModel == null)
  8. {
  9. 9 return YMHttpResponseMessage.ServerDataValidateErrorMessage;
  10. }
  11.  
  12. string returnvalue = NotificationBusiness.NotificationSmS(inputModel.OrderNo, inputModel.BusinessType, inputModel.SendNodeCode);
  13.  
  14. if (returnvalue == "")
  15. {
  16. return YMHttpResponseMessage.GetMessage(CustomeHttpMessageResponseStatusCode.SUCCESS, "发送成功");
  17. }
  18. else if (returnvalue == "-1")
  19. {
  20. return YMHttpResponseMessage.GetMessage(CustomeHttpMessageResponseStatusCode.INTERNALSERVERERROR, "此订单不存在");
  21. }
  22. else if (returnvalue == "-2")
  23. {
  24. return YMHttpResponseMessage.GetMessage(CustomeHttpMessageResponseStatusCode.INTERNALSERVERERROR, "不存在通知");
  25. }
  26. else if (returnvalue == "-3")
  27. {
  28. return YMHttpResponseMessage.GetMessage(CustomeHttpMessageResponseStatusCode.INTERNALSERVERERROR, "不存在预定人");
  29. }
  30. else
  31. {
  32. return YMHttpResponseMessage.GetMessage(CustomeHttpMessageResponseStatusCode.INTERNALSERVERERROR, "发送失败");
  33. }
  34. }

因为是非法的json串,所以inputModel的值是null。经过持续调试程序,小组里一个细心的同学发现,重新运行项目后第一次调试时,执行第9行;而在之后再调试时,是不走第9行的。

这时就可以判断,问题可能出在第9行了。

再次排查发现YMHttpResponseMessageServerDataValidateErrorMessage是静态的

  1. public class YMHttpResponseMessage
  2. {
  3. /// <summary>
  4. /// 服务资源未找到
  5. /// </summary>
  6. public static HttpResponseMessage ServiceNotFoundMessage;
  7.  
  8. /// <summary>
  9. /// 服务器异常
  10. /// </summary>
  11. public static HttpResponseMessage ServerErrorMessage;
  12.  
  13. /// <summary>
  14. /// 数据格式不附
  15. /// </summary>
  16. public static HttpResponseMessage ServerDataValidateErrorMessage;
  17.  
  18. static YMHttpResponseMessage()
  19. {
  20. ....

  21. ServerDataValidateErrorMessage = new HttpResponseMessage()
  22. {
  23. Content = new StringContent(
  24. new ResponseModel()
  25. {
  26. data = Newtonsoft.Json.JsonConvert.SerializeObject(null),
  27. status = CustomeHttpMessageResponseStatusCode.INTERNALSERVERERROR,
  28. message = "数据格式不附"
  29. }.ToString(),
  30. Encoding.UTF8,
  31. "application/json"
  32. ),
  33. StatusCode = System.Net.HttpStatusCode.InternalServerError
  34. };
  35. }

再与那个没问题的API项目源码比较,发现其里面每次处理请求的返回值都是new一个HttpResponseMessage对象。

这样,就大概知道原因了, Http请求是无状态的,.net CLR每次处理完请求后,会利用其自带的垃圾回收机制,将托管的对象释放掉。 而static修饰的对象,其一直保存在内存中。虽然被释放掉了,但对象还存在。 所以,就抛出了题目中提到的异常:System.ObjectDisposedException: 无法访问已释放的对象

那么,看来,是static惹的祸了。赶紧改程序修复后,发现问题解决了。

我们小组那个同学不禁长吁,大家用static还是小心为好。

记得以前在电商公司做项目时,就遇到类似static导致系统问题的情况。当然,再次强调,读书少是要吃亏的, 需弄明白static这些用法才是王道。

最后,吐槽一下博客园。

我初用博客园是在2011年也就是我入职那家电商公司后,认识了很多牛逼的程序猿,大家都用博客园,我自然也跟着用。这样显得牛逼嘛。 当时写过一篇随笔,js啊,我被open撞了一下腰, 看到半小时内很多点击量,也有很多人跟着来讨论,甚是兴奋。后来的一些随笔,越来越少有那种热闹劲儿了。 当然,跟自己能力有很大关系。  如今自己也持续了这么多年每月写作,在这里,给自己点一个赞,只为这份坚持。

static,你还敢用吗?的更多相关文章

  1. boost的link 和 runtime-link,搭配shared 和 static

    转自:http://blog.csdn.net/yasi_xi/article/details/8660549 link:生成动态链接库/静态链接库.生成动态链接库需使用shared方式,生成静态链接 ...

  2. Static NAT with iptables on Linux

    本文的名字取的比较有意义,因为本文并不是真的要讨论如何在Linux上使用iptables实现static nat!之所以这么命名本文,是想引起别人的注意,因为中文资料,以及国内的搜索引擎,基本上没有人 ...

  3. java中关键字static和final

    面向对象的不足 凡是有利必有弊,强对象编程,使得语法简单统一,但也有其缺点,而且有很多.我们在接下来的课程里会一点点接触到.我们今天先看第一个. 有些变量和函数确实没必要定义在一个类里.强行规定这些函 ...

  4. 浅谈this和static

    一.this关键字 一个比较经典的使用: 输出的结果是:12 1.this关键字只能在方法的内部使用,表示对“调用方法的那个对象”的引用,this的用法和其他对象引用并无不同.注意一点:在方法的内部调 ...

  5. link 和 runtime-link,搭配shared 和 static(转)

    原文转自 http://blog.csdn.net/yasi_xi/article/details/8660549 参考: http://bbs.sjtu.edu.cn/bbscon,board,C, ...

  6. Java中static修饰类的问题

    Java中static修饰类的问题 众所周知,Java中static关键字可以修饰方法与变量: 修饰变量的时候,这个变量属于类变量,可以直接通过类名.变量名来引用. 修饰方法的时候可以直接通过类名.方 ...

  7. static关键字真能提高Bean的优先级吗?答:真能

    生命太短暂,不要去做一些根本没有人想要的东西.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习 ...

  8. static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类

    生命太短暂,不要去做一些根本没有人想要的东西.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习 ...

  9. PHP 面向对象编程和设计模式 (2/5) - 静态变量、属性和方法及延迟绑定

    PHP高级程序设计 学习笔记 2014.06.10 Static(静态)关键字用来定义静态方法和属性,static 也可用于定义静态变量以及后期静态绑定. 1.静态变量 static variable ...

  10. JavaSE基础第四篇

    1.参数传递   2,方法的重载 方法的参数的个数.类型.顺序 跟修饰符.返回值无关   3.构造方法: return 表示当前方法执行结束,后面不能写任何语句   4工程导入 单个.java文件粘贴 ...

随机推荐

  1. 按需加载.js .css文件

    首先,理解按需加载当你需要用到某个js里面的函数什么鬼,或者某个css里的样式的时候你才开始加载这个文件. 然后是怎样实现的,简单来说就是在js中动态的createElem<script> ...

  2. WPF 有用博客地址

    增加智能感知的RichTextBox扩展控件(WPF) WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式.水印.Label标签. ...

  3. Angular企业级开发(2)-搭建Angular开发环境

    1.集成开发环境 个人或团队开发AngularJS项目时,有很多JavaScript编辑器可以选择.使用优秀的集成开发环境(Integrated Development Environment)能节省 ...

  4. Angular企业级开发(1)-AngularJS简介

    AngularJS介绍 AngularJS是一个功能完善的JavaScript前端框架,同时是基于MVC(Model-View-Controller理念的框架,使用它能够高效的开发桌面web app和 ...

  5. VS15 preview 5打开文件夹自动生成slnx.VC.db SQLite库疑惑?求解答

    用VS15 preview 5打开文件夹(详情查看博客http://www.cnblogs.com/zsy/p/5962242.html中配置),文件夹下多一个slnx.VC.db文件,如下图: 本文 ...

  6. .NET面试题集锦①(Part一)

    一.前言部分 文中的问题及答案多收集整理自网络,不保证100%准确,还望斟酌采纳. 1.面向对象的思想主要包括什么? 答:任何事物都可以理解为对象,其主要特征: 继承.封装.多态.特点:代码好维护,安 ...

  7. Android之数据存储的五种方法

    1.Android数据存储的五种方法 (1)SharedPreferences数据存储 详情介绍:http://www.cnblogs.com/zhangmiao14/p/6201900.html 优 ...

  8. MongoDB学习笔记五—查询上

    数据准备 { , "goods_name" : "KD876", "createTime" : ISODate("2016-12- ...

  9. Linux常用命令

    命令格式与目录处理命令 ls 命令格式与目录处理命令 ls 命令格式:命令 [-选项][参数] 例:ls -la /etc 说明: 1)个别命令使用不遵循格式 2)当有多个选项时,可以写在一起 3)简 ...

  10. Linux下部署ASP.NET服务连接oracle遇到的问题记录

    一.如何卸载MONO Q:mono是linux系统上跨平台软件,卸载它有两种方式: 1.知道mono安装路径,安装原来的路径直接覆盖安装(最为简单): 2.不知道mono安装路径,首先通过sudo f ...