buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8

统计

static,你还敢用吗?

 

▄︻┻┳═一「static」Agenda:

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

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

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


 

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

System.ObjectDisposedException: 无法访问已释放的对象。
对象名:“System.Net.Http.StringContent”。
   在 System.Web.Http.WebHost.HttpControllerHandler.EndProcessRequest(IAsyncResult result)
   在 System.Web.Http.WebHost.HttpControllerHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
   在 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


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


复制代码

 

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

 

回到顶部

如下是排障过程:

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

复制代码
public class CustomMessageHandler : DelegatingHandler
{
        protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            if (!request.RequestUri.ToString().ToLower().Contains("swagger"))
            {
                SysLogToFile.WriteNotificationMessage(string.Format("请求url:{0}\r\n请求参数:{1}\r\n", 
                    request.RequestUri, request.Content.ReadAsStringAsync().Result));
            }
            return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>(task =>
            {
                if (!request.RequestUri.ToString().ToLower().Contains("swagger"))
                {
                    SysLogToFile.WriteNotificationMessage(string.Format("请求url:{0}\r\n请求参数:{1}\r\n响应结果:{2}",
                        request.RequestUri, request.Content.ReadAsStringAsync().Result, task.Result.Content.ReadAsStringAsync().Result));
                }
                return task.Result;
            });
        }
}
复制代码

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

复制代码
#region 程序集 System.Net.Http.dll, v2.0.0.0
// D:\SourceProject\notification\trunk\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll
#endregion

using System;
using System.IO;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace System.Net.Http
{
    // 摘要: 
    //     表示 HTTP 实体正文和内容标头的基类。
    public abstract class HttpContent : IDisposable
    {
        // 摘要: 
        //     初始化 System.Net.Http.HttpContent 类的新实例。
        protected HttpContent();
        //
        // 摘要: 
        //     释放由 System.Net.Http.HttpContent 使用的非托管资源和托管资源。
        public void Dispose();
        //
        // 摘要: 
        //     释放由 System.Net.Http.HttpContent 使用的非托管资源,并可根据需要释放托管资源。
        //
        // 参数: 
        //   disposing:
        //     如果为 true,则释放托管资源和非托管资源;如果为 false,则仅释放非托管资源。
        protected virtual void Dispose(bool disposing);
        //
        // 摘要: 
        //     以异步操作将 HTTP 内容写入流。
        //
        // 返回结果: 
        //     返回 System.Threading.Tasks.Task<TResult>。 表示异步操作的任务对象。
        public Task<string> ReadAsStringAsync();
    }
}
复制代码

 

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

                    string log = string.Format("请求url:{0}\r\n请求参数:{1}\r\n响应结果:{2}",
                        request.RequestUri, request.Content.ReadAsStringAsync().Result, task.Result.Content.ReadAsStringAsync().Result);
                    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             return YMHttpResponseMessage.ServerDataValidateErrorMessage;
10         }
11 
12         string returnvalue = NotificationBusiness.NotificationSmS(inputModel.OrderNo, inputModel.BusinessType, inputModel.SendNodeCode);
13 
14         if (returnvalue == "1")
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是静态的

复制代码
public class YMHttpResponseMessage
    {
        /// <summary>
        /// 服务资源未找到
        /// </summary>
        public static HttpResponseMessage ServiceNotFoundMessage;

        /// <summary>
        /// 服务器异常
        /// </summary>
        public static HttpResponseMessage ServerErrorMessage;

        /// <summary>
        /// 数据格式不附
        /// </summary>
        public static HttpResponseMessage ServerDataValidateErrorMessage;

        static YMHttpResponseMessage()
        {
....
ServerDataValidateErrorMessage = new HttpResponseMessage() { Content = new StringContent( new ResponseModel() { data = Newtonsoft.Json.JsonConvert.SerializeObject(null), status = CustomeHttpMessageResponseStatusCode.INTERNALSERVERERROR, message = "数据格式不附" }.ToString(), Encoding.UTF8, "application/json" ), StatusCode = System.Net.HttpStatusCode.InternalServerError };
}
复制代码

 

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

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

回到顶部

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

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

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

回到顶部

最后,吐槽一下博客园。  

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

 

posted on   buguge  阅读(632)  评论(0编辑  收藏  举报

编辑推荐:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
点击右上角即可分享
微信分享提示