在操作方法中抛出 HttpResponseException 时处理 HttpResponseMessage 及其内容
Disposing HttpResponseMessage and its Content when throwing HttpResponseException inside action method
我的问题的来源是以下代码,它是 Microsoft documentation 中包含的代码片段的一部分,用于 asp.net web api 中的异常处理:
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);
HttpResponseMessage
和StringContent
都实现了IDisposable
接口,但是上面代码中没有调用方法IDisposable.Dispose
.
这是个问题吗?不处理这些对象是否有任何副作用?
根据 this article 的说法,可能的解决方案是将上述代码更改为以下内容:
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);
这是否真的有必要,或者是否有可能避免这种情况(根据 Microsoft 文档中显示的内容)?
检查Microsoft Source for 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 的源代码会产生这个可爱的 gem:
/// <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
租用缓冲区,你必须return它,否则会导致内存泄漏。
所以是的,我想说尊重 IDisposable
在这里非常重要。
由 HttpResponseException
包装的响应将由 asp.net 框架处理,就像您 return 从您的操作中得到的任何其他响应一样。您可以通过创建虚拟响应消息轻松地自行测试:
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 控制器中)。
请注意,此异常由负责调用您的 api 控制器操作的 ApiControllerActionInvoker
、class 捕获。然后它只是抓取 yourException.Response
并通过管道将其向前推,因此抛出此异常与仅 return 来自您的 api 控制器操作的相应响应没有什么不同。应该清楚的是,我认为框架将在处理完所有这些响应后将其处理掉。否则这将是非常糟糕的设计。
所以,不要让那些 RegisterForDispose
使您的代码混乱,让框架为您处理。
我的问题的来源是以下代码,它是 Microsoft documentation 中包含的代码片段的一部分,用于 asp.net web api 中的异常处理:
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);
HttpResponseMessage
和StringContent
都实现了IDisposable
接口,但是上面代码中没有调用方法IDisposable.Dispose
.
这是个问题吗?不处理这些对象是否有任何副作用?
根据 this article 的说法,可能的解决方案是将上述代码更改为以下内容:
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);
这是否真的有必要,或者是否有可能避免这种情况(根据 Microsoft 文档中显示的内容)?
检查Microsoft Source for 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 的源代码会产生这个可爱的 gem:
/// <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
租用缓冲区,你必须return它,否则会导致内存泄漏。
所以是的,我想说尊重 IDisposable
在这里非常重要。
由 HttpResponseException
包装的响应将由 asp.net 框架处理,就像您 return 从您的操作中得到的任何其他响应一样。您可以通过创建虚拟响应消息轻松地自行测试:
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 控制器中)。
请注意,此异常由负责调用您的 api 控制器操作的 ApiControllerActionInvoker
、class 捕获。然后它只是抓取 yourException.Response
并通过管道将其向前推,因此抛出此异常与仅 return 来自您的 api 控制器操作的相应响应没有什么不同。应该清楚的是,我认为框架将在处理完所有这些响应后将其处理掉。否则这将是非常糟糕的设计。
所以,不要让那些 RegisterForDispose
使您的代码混乱,让框架为您处理。