将 HttpClient 设置为太短的超时会导致进程崩溃

Posted

技术标签:

【中文标题】将 HttpClient 设置为太短的超时会导致进程崩溃【英文标题】:Setting HttpClient to a too short timeout crashes process 【发布时间】:2013-03-12 14:10:14 【问题描述】:

我注意到,当我使用 System.Net.HttpClient 并有短暂的超时时,有时它可能会导致进程崩溃,即使它被包装在 try-catch 块中也是如此。这是一个重现这一点的简短程序。

public static void Main(string[] args)

    var tasks = new List<Task>();
    for (int i = 0; i < 1000; i++)
    
        tasks.Add(MakeHttpClientRequest());
    
    Task.WaitAll(tasks.ToArray());



private async static Task MakeHttpClientRequest()
            
    var httpClient = new HttpClient  Timeout = TimeSpan.FromMilliseconds(1) ;
    var request = "whatever";
    try
    
        HttpResponseMessage result =
            await httpClient.PostAsync("http://www.flickr.com/services/rest/?method=flickr.test.echo&format=json&api_key=766c0ac7802d55314fa980727f747710",
                                 new StringContent(request));             
        await result.Content.ReadAsStringAsync();                
    
    catch (Exception x)
    
        Console.WriteLine("Error occurred but it is swallowed: " + x);
    

运行此程序将使进程崩溃,并出现以下异常:

Unhandled Exception: System.AggregateException: One or more errors occurred. ---> System.Net.WebException: The request was canceled
   at System.Net.ServicePointManager.FindServicePoint(Uri address, IWebProxy proxy, ProxyChain& chain, HttpAbortDelegate& abortDelegate, Int32& abortState)
   at System.Net.HttpWebRequest.FindServicePoint(Boolean forceFind)
   at System.Net.HttpWebRequest.get_ServicePoint()
   at System.Net.AuthenticationState.PrepareState(HttpWebRequest httpWebRequest)
   at System.Net.AuthenticationState.ClearSession(HttpWebRequest httpWebRequest)
   at System.Net.HttpWebRequest.ClearAuthenticatedConnectionResources()
   at System.Net.HttpWebRequest.Abort(Exception exception, Int32 abortState)
   at System.Net.HttpWebRequest.Abort()
   at System.Net.Http.HttpClientHandler.OnCancel(Object state)
   at System.Threading.CancellationCallbackInfo.ExecutionContextCallback(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.CancellationCallbackInfo.ExecuteCallback()
   at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
   at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
   --- End of inner exception stack trace ---
   at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
   at System.Threading.CancellationTokenSource.NotifyCancellation(Boolean throwOnFirstException)
   at System.Threading.CancellationTokenSource.TimerCallbackLogic(Object obj)
   at System.Threading.TimerQueueTimer.CallCallbackInContext(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.TimerQueueTimer.CallCallback()
   at System.Threading.TimerQueueTimer.Fire()
   at System.Threading.TimerQueue.FireNextTimers()
   at System.Threading.TimerQueue.AppDomainTimerCallback()

稍微深入一点,似乎当HttpClient 在创建相关的ServicePoint 之前中止请求时,HttpWebRequest 会尝试通过ServicePointManager.FindServicePoint 创建ServicePoint,这会引发 RequestCanceled。由于这个异常是在试图取消请求的线程中抛出的,所以没有被捕获,进程就死掉了。

我错过了什么吗?你遇到过这个问题吗?

【问题讨论】:

这是一个惊人的错误。 a) 你能尝试在整个 Main 周围添加一个 try-catch 吗? b) 你能钩住TaskScheduler.UnobservedTaskException 事件吗? @usr:两者都试过了,正如预期的那样,它没有帮助。 main 上的 try-catch 只捕获主线程的东西,进程崩溃异常不是 UnobservedTaskException。 你不能捕捉到聚合异常吗?并从那里处理它?捕捉(AggregateException ae) @PaulG:我不能,它是从另一个我无法控制的线程抛出的。 我认为这个问题也可能发生在更大的超时时间 【参考方案1】:

HttpWebRequest.Abort() 在后台/计时器线程上引发异常。这与HttpClient的Task管理无关。

HttpWebRequest.Abort() 的异常应在 .NET 4.5 GDR1 中修复。 http://support.microsoft.com/kb/2750149 http://support.microsoft.com/kb/2750147

【讨论】:

绝对是补丁问题。我可以确认这在我的最新修补过的 Windows 8 修补笔记本电脑上可以正常工作,但在我的工作笔记本电脑上无法正常工作,因为它接收了来自公司更新的补丁。 如果您无法安装更新,有什么选择吗? (也许回到旧的 HttpWebRequest ?)【参考方案2】:

看起来这是 HttpClient 的异步处理程序如何管理任务的某种错误。我能够并行启动这些项目,但同步运行它们并且它可以工作。我不确定您是否只想防止未处理的错误。这运行了并行任务,但是自从我将其关闭后,它们实际上并不是异步的。在我的电脑上,我总是打到 5 轮,然后它就会崩溃。即使我在一秒钟后将其设置为超时,如果线程中的崩溃是异步的,它们仍然会爆炸。

我认为这是一个错误,我无法想象这是预期的行为。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;

namespace TestCrash

    class Program
    
        static void Main(string[] args)
        
            try
            
                Parallel.ForEach(Enumerable.Range(1, 1000).ToList(), i =>
                
                    Console.WriteLine(i);
                    using (var c = new HttpClient  Timeout = TimeSpan.FromMilliseconds(1) )
                    
                        var t = c.GetAsync("http://microsoft.com");
                        t.RunSynchronously(); //<--comment this line and it crashes
                        Console.WriteLine(t.Result);
                    
                );
            
            catch (Exception x)
            
                Console.WriteLine(x.Message);
            
            Console.ReadKey();
        
    

【讨论】:

似乎 HttpWebRequest 中存在错误(HttpClient 在其发送中使用该错误)。请参阅 Tratcher 的回答。 不应该真正将 Parallel.ForEach 与异步一起使用。这里是怪物。

以上是关于将 HttpClient 设置为太短的超时会导致进程崩溃的主要内容,如果未能解决你的问题,请参考以下文章

Apache HttpClient 4.3 - 设置连接空闲超时

Apache HttpClient 4.0 无法在 Android 上的套接字超时

如何在并发中给 HttpClient 设置不同的超时时间?

httpclient: 设置请求的超时时间,连接超时时间等

接口调试工具ApiPost的发送超时时间设置方法

巧用 Base62 解决字段太短的问题