在 action 方法内抛出 HttpResponseException 时,应处理 HttpResponseMessage 及其内容

4
我的问题来源于以下代码,它是包含在 Microsoft 文档中的代码片段的一部分,用于 asp.net web api 的异常处理:Microsoft documentation
var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
{
    Content = new StringContent(string.Format("No product with ID = {0}", id)),
    ReasonPhrase = "Product ID Not Found"
};
throw new HttpResponseException(resp);

在上面的代码中,HttpResponseMessageStringContent都实现了IDisposable接口,但是没有一个调用IDisposable.Dispose方法。

这是一个问题吗?不释放这些对象会有什么副作用吗?

根据这篇文章,可能的解决方法是将上面的代码更改为以下内容:

var content = new StringContent(string.Format("No product with ID = {0}", id));
var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
{
    Content = content,
    ReasonPhrase = "Product ID Not Found"
};

this.Request.RegisterForDispose(content);
this.Request.RegisterForDispose(resp);

throw new HttpResponseException(resp);

这真的必要吗?或者根据微软文档中所展示的,有没有可能避免这种情况?

2个回答

4

查看Microsoft Source中的HttpResponseMessage.CS

protected virtual void Dispose(bool disposing)
{
    // The reason for this type to implement IDisposable is that it contains instances of 
    // types that implement IDisposable (content). 
    if (disposing && !_disposed)
    {
        _disposed = true;
        if (_content != null)
        {
            _content.Dispose();
        }
    }
}

content 是 HttpContent 类型。查看 Microsoft Source for HttpContent.cs

protected override void Dispose(bool disposing)
{
    Debug.Assert(_buffer != null);

    ArrayPool<byte>.Shared.Return(_buffer);
    _buffer = null;

    base.Dispose(disposing);
}

ArrayPool 的注释如下:

/// Renting and returning buffers with an <see cref="ArrayPool{T}"/> can increase performance
/// in situations where arrays are created and destroyed frequently, resulting in significant
/// memory pressure on the garbage collector.

检查ArrayPool源代码,可以发现以下精华内容:
    /// <summary>
    /// Retrieves a shared <see cref="ArrayPool{T}"/> instance.
    /// </summary>
    /// <remarks>
    /// The shared pool provides a default implementation of <see cref="ArrayPool{T}"/>
    /// that's intended for general applicability.  It maintains arrays of multiple sizes, and 
    /// may hand back a larger array than was actually requested, but will never hand back a smaller 
    /// array than was requested. Renting a buffer from it with <see cref="Rent"/> will result in an 
    /// existing buffer being taken from the pool if an appropriate buffer is available or in a new 
    /// buffer being allocated if one is not available.
    /// </remarks>
    public static ArrayPool<T> Shared
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get { return Volatile.Read(ref s_sharedInstance) ?? EnsureSharedCreated(); }
    }

ArrayPool 不使用 WeakReference 或任何类似的机制来确保正确处理。如果您从 ArrayPool.Shared 租用缓冲区,则必须将其归还,否则会导致内存泄漏。

所以,确保尊重 IDisposable 在这里非常重要。


1
但是OP谈论的是asp.net web api(因此,完整的.NET),而您指向了.net core的源代码(据我记得,它甚至没有这个异常)。更不用说处理此异常的任何内容(毕竟它是特殊的)也可能会处理包装在其中的响应。 - Evk
@Evk:很有可能完整的.NET源代码非常相似,如果不是完全一样的话。检查起来也不难。 - Robert Harvey
1
我怀疑,由于HttpResponseException在.Net Core中不存在,而ArrayPool在asp.net mvc(不是核心)中也没有使用,因此它在那时不存在。 - Evk
1
@Evk:你说得对。我去用JustDecompile查看了.NET Framework System.Web.Http dll的源代码,发现那里没有ArrayPool。有几个流被处理掉了,但很难判断让它们自然垃圾回收是否会产生负面影响。 - Robert Harvey
@Evk 乍一看,我没有注意到你上面链接的源代码与 .NET Core 相关。我不知道 asp.net web api 如何处理 HttpResponseException 类型的异常,但肯定有内置的机制来处理这种异常,因为客户端收到的响应在状态码和内容方面都是符合预期的。也许我们谈论的资源会在适当的时候由框架自动释放? - Enrico Massone

2

HttpResponseException包装的响应将被ASP.NET框架处理,就像您从操作中返回的任何其他响应一样。您可以通过创建虚拟响应消息轻松测试自己:

class DummyResponse : HttpResponseMessage {
    public DummyResponse(HttpStatusCode statusCode) : base(statusCode) {
    }

    protected override void Dispose(bool disposing) {
        Console.WriteLine("dispose called");
        base.Dispose(disposing);
    }
}

然后使用这个响应抛出HttpResponseException并在Dispose覆盖中设置断点。您会发现调用了Dispose,如果查看调用堆栈,您会发现HttpControllerHandler负责处理该问题(在asp.net web api控制器中)。
请注意,ApiControllerActionInvoker捕获此异常,该类负责调用api控制器操作。然后,它只获取yourException.Response并将其推送到管道中,因此抛出此异常与仅从api控制器操作返回相应的响应没有区别。我认为应该很清楚,框架在完成它们时会处置所有这些响应。否则,设计会非常糟糕。
因此,请不要用那些RegisterForDispose来混淆您的代码,让框架为您处理这些内容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接