在 HttpWebRequest.Abort 下请求取消异常
Request canceled exception under HttpWebRequest.Abort
我的应用程序每隔几秒就向我的服务器查询更新。
离开它 运行 大约 3 天后,我观察到该应用程序崩溃并显示以下堆栈跟踪。
如您所知,当在工作线程中获取异常时,无法捕获它,因此我的应用程序崩溃了。
System.Net.WebException: The request was canceled
System.Net.ServicePointManager.FindServicePoint(Uri address, IWebProxy proxy, ProxyChain& chain, HttpAbortDelegate& abortDelegate, Int32& abortState)
System.Net.HttpWebRequest.FindServicePoint(Boolean forceFind)
System.Net.AuthenticationState.PrepareState(HttpWebRequest httpWebRequest)
System.Net.AuthenticationState.ClearSession(HttpWebRequest httpWebRequest)
System.Net.HttpWebRequest.ClearAuthenticatedConnectionResources()
System.Net.HttpWebRequest.Abort(Exception exception, Int32 abortState)
System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
System.Threading.ThreadPoolWorkQueue.Dispatch()
我在网络上看到过很多类似的帖子。但是所有拥有相同堆栈的人都没有得到任何帮助。
我也看到很多人建议将我的 HttpWebRequest
的 属性 设置为 KeepAlive=false
,但是,这可能会影响我的表现并且是不可接受的。
事实上,这是 Microsoft 框架中的一个已知错误,此处提到的广告:
https://support.microsoft.com/en-us/kb/2750147
关于这个的奇怪问题是我的应用程序是 运行 .NET4.0 而不是 .NET4.5
在与 Microsoft 的支持人员交谈后,似乎如果在机器上 安装 .NET4.5,则应用程序的行为会发生变化。所以它实际上是有道理的。
可以在 MS 的源代码中找到证据。来自 http://referencesource.microsoft.com/#q=httpwebrequest :
// TimeoutCallback - Called by the TimerThread to abort a request. This just posts ThreadPool work item - Abort() does too
// much to be done on the timer thread (timer thread should never block or call user code).
private static void TimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context)
{
ThreadPool.UnsafeQueueUserWorkItem(s_AbortWrapper, context);
}
private void Abort(Exception exception, int abortState)
{
GlobalLog.ThreadContract(ThreadKinds.Unknown, "HttpWebRequest#" + ValidationHelper.HashString(this) + "::Abort()");
if (Logging.On) Logging.Enter(Logging.Web, this, "Abort", (exception == null? "" : exception.Message));
if(Interlocked.CompareExchange(ref m_Aborted, abortState, 0) == 0) // public abort will never drain streams
{
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::Abort() - " + exception);
NetworkingPerfCounters.Instance.Increment(NetworkingPerfCounterName.HttpWebRequestAborted);
m_OnceFailed = true;
CancelTimer();
WebException webException = exception as WebException;
if (exception == null)
{
webException = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled);
}
else if (webException == null)
{
webException = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), exception, WebExceptionStatus.RequestCanceled, _HttpResponse);
}
try
{
#if DEBUG
bool setResponseCalled = false;
try
{
#endif
// Want to make sure that other threads see that we're aborted before they set an abort delegate, or that we see
// the delegate if they might have missed that we're aborted.
Thread.MemoryBarrier();
HttpAbortDelegate abortDelegate = _AbortDelegate;
#if DEBUG
m_AbortDelegateUsed = abortDelegate == null ? (object)DBNull.Value : abortDelegate;
#endif
if (abortDelegate == null || abortDelegate(this, webException))
{
// We don't have a connection associated with this request
#if DEBUG
setResponseCalled = true;
#endif
SetResponse(webException);
}
else
{
// In case we don't call SetResponse(), make sure to complete the lazy async result
// objects. abortDelegate() may not end up in a code path that would complete these
// objects.
LazyAsyncResult writeAResult = null;
LazyAsyncResult readAResult = null;
if (!Async)
{
lock (this)
{
writeAResult = _WriteAResult;
readAResult = _ReadAResult;
}
}
if (writeAResult != null)
writeAResult.InvokeCallback(webException);
if (readAResult != null)
readAResult.InvokeCallback(webException);
}
if (!Async)
{
LazyAsyncResult chkConnectionAsyncResult = ConnectionAsyncResult;
LazyAsyncResult chkReaderAsyncResult = ConnectionReaderAsyncResult;
if (chkConnectionAsyncResult != null)
chkConnectionAsyncResult.InvokeCallback(webException);
if (chkReaderAsyncResult != null)
chkReaderAsyncResult.InvokeCallback(webException);
}
if (this.IsWebSocketRequest && this.ServicePoint != null)
{
this.ServicePoint.CloseConnectionGroup(this.ConnectionGroupName);
}
#if DEBUG
}
catch (Exception stressException)
{
t_LastStressException = stressException;
if (!NclUtilities.IsFatal(stressException)){
GlobalLog.Assert(setResponseCalled, "HttpWebRequest#{0}::Abort|{1}", ValidationHelper.HashString(this), stressException.Message);
}
throw;
}
#endif
}
catch (InternalException)
{
}
}
if(Logging.On)Logging.Exit(Logging.Web, this, "Abort", "");
}
如您所见,TimeoutCallback
正在新线程中调用 abort 方法,这意味着它不是异常证明。
此外,Abort
在某些场景下可能会抛出异常。理论上,这可以很容易地重现。
我的应用程序每隔几秒就向我的服务器查询更新。
离开它 运行 大约 3 天后,我观察到该应用程序崩溃并显示以下堆栈跟踪。
如您所知,当在工作线程中获取异常时,无法捕获它,因此我的应用程序崩溃了。
System.Net.WebException: The request was canceled
System.Net.ServicePointManager.FindServicePoint(Uri address, IWebProxy proxy, ProxyChain& chain, HttpAbortDelegate& abortDelegate, Int32& abortState)
System.Net.HttpWebRequest.FindServicePoint(Boolean forceFind)
System.Net.AuthenticationState.PrepareState(HttpWebRequest httpWebRequest)
System.Net.AuthenticationState.ClearSession(HttpWebRequest httpWebRequest)
System.Net.HttpWebRequest.ClearAuthenticatedConnectionResources()
System.Net.HttpWebRequest.Abort(Exception exception, Int32 abortState)
System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
System.Threading.ThreadPoolWorkQueue.Dispatch()
我在网络上看到过很多类似的帖子。但是所有拥有相同堆栈的人都没有得到任何帮助。
我也看到很多人建议将我的 HttpWebRequest
的 属性 设置为 KeepAlive=false
,但是,这可能会影响我的表现并且是不可接受的。
事实上,这是 Microsoft 框架中的一个已知错误,此处提到的广告:
https://support.microsoft.com/en-us/kb/2750147
关于这个的奇怪问题是我的应用程序是 运行 .NET4.0 而不是 .NET4.5
在与 Microsoft 的支持人员交谈后,似乎如果在机器上 安装 .NET4.5,则应用程序的行为会发生变化。所以它实际上是有道理的。
可以在 MS 的源代码中找到证据。来自 http://referencesource.microsoft.com/#q=httpwebrequest :
// TimeoutCallback - Called by the TimerThread to abort a request. This just posts ThreadPool work item - Abort() does too
// much to be done on the timer thread (timer thread should never block or call user code).
private static void TimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context)
{
ThreadPool.UnsafeQueueUserWorkItem(s_AbortWrapper, context);
}
private void Abort(Exception exception, int abortState)
{
GlobalLog.ThreadContract(ThreadKinds.Unknown, "HttpWebRequest#" + ValidationHelper.HashString(this) + "::Abort()");
if (Logging.On) Logging.Enter(Logging.Web, this, "Abort", (exception == null? "" : exception.Message));
if(Interlocked.CompareExchange(ref m_Aborted, abortState, 0) == 0) // public abort will never drain streams
{
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::Abort() - " + exception);
NetworkingPerfCounters.Instance.Increment(NetworkingPerfCounterName.HttpWebRequestAborted);
m_OnceFailed = true;
CancelTimer();
WebException webException = exception as WebException;
if (exception == null)
{
webException = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled);
}
else if (webException == null)
{
webException = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled), exception, WebExceptionStatus.RequestCanceled, _HttpResponse);
}
try
{
#if DEBUG
bool setResponseCalled = false;
try
{
#endif
// Want to make sure that other threads see that we're aborted before they set an abort delegate, or that we see
// the delegate if they might have missed that we're aborted.
Thread.MemoryBarrier();
HttpAbortDelegate abortDelegate = _AbortDelegate;
#if DEBUG
m_AbortDelegateUsed = abortDelegate == null ? (object)DBNull.Value : abortDelegate;
#endif
if (abortDelegate == null || abortDelegate(this, webException))
{
// We don't have a connection associated with this request
#if DEBUG
setResponseCalled = true;
#endif
SetResponse(webException);
}
else
{
// In case we don't call SetResponse(), make sure to complete the lazy async result
// objects. abortDelegate() may not end up in a code path that would complete these
// objects.
LazyAsyncResult writeAResult = null;
LazyAsyncResult readAResult = null;
if (!Async)
{
lock (this)
{
writeAResult = _WriteAResult;
readAResult = _ReadAResult;
}
}
if (writeAResult != null)
writeAResult.InvokeCallback(webException);
if (readAResult != null)
readAResult.InvokeCallback(webException);
}
if (!Async)
{
LazyAsyncResult chkConnectionAsyncResult = ConnectionAsyncResult;
LazyAsyncResult chkReaderAsyncResult = ConnectionReaderAsyncResult;
if (chkConnectionAsyncResult != null)
chkConnectionAsyncResult.InvokeCallback(webException);
if (chkReaderAsyncResult != null)
chkReaderAsyncResult.InvokeCallback(webException);
}
if (this.IsWebSocketRequest && this.ServicePoint != null)
{
this.ServicePoint.CloseConnectionGroup(this.ConnectionGroupName);
}
#if DEBUG
}
catch (Exception stressException)
{
t_LastStressException = stressException;
if (!NclUtilities.IsFatal(stressException)){
GlobalLog.Assert(setResponseCalled, "HttpWebRequest#{0}::Abort|{1}", ValidationHelper.HashString(this), stressException.Message);
}
throw;
}
#endif
}
catch (InternalException)
{
}
}
if(Logging.On)Logging.Exit(Logging.Web, this, "Abort", "");
}
如您所见,TimeoutCallback
正在新线程中调用 abort 方法,这意味着它不是异常证明。
此外,Abort
在某些场景下可能会抛出异常。理论上,这可以很容易地重现。