HttpWebRequest.Abort 下的请求取消异常

Posted

技术标签:

【中文标题】HttpWebRequest.Abort 下的请求取消异常【英文标题】:Request canceled exception under HttpWebRequest.Abort 【发布时间】:2015-04-20 11:22:53 【问题描述】:

我的应用程序每隔几秒就查询我的服务器以获取更新。

在让它运行大约 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,但是这可能会损害我的性能并且是不可接受的。

【问题讨论】:

【参考方案1】:

这实际上是微软框架中的一个已知错误,此处提到的广告:

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 在某些情况下可能会抛出异常。理论上,这很容易被复制。

【讨论】:

以上是关于HttpWebRequest.Abort 下的请求取消异常的主要内容,如果未能解决你的问题,请参考以下文章

请求标头的客户端部分下的“接受:*/*”是啥意思?

php下的原生ajax请求

实际情况下的异步请求处理如何工作?

为 APIM 下的所有 API 实例创建请求速率限制

NetCore下的HTTP请求IHttpClientFactory

restful规范下的ajax请求