Setting HttpClient to a too short timeout crashes

2019-03-08 14:18发布

I've noticed that when I'm using System.Net.HttpClient with a short timeout, it may sometimes crash the process, even when it is wrapped in a try-catch block. Here's a short program to reproduce this.

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);
    }
}

Running this will crash the process with the following exception:

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()

Digging in a little, it seems that when HttpClient aborts the request before a relevant ServicePoint is created, HttpWebRequest attempts to create the ServicePoint, via ServicePointManager.FindServicePoint, which throws a RequestCanceled. Since this exception is thrown in the thread that attempts to cancel the request, it is not caught, and the process dies.

Am I missing something? Have you run into this issue?

2条回答
Animai°情兽
2楼-- · 2019-03-08 14:46

HttpWebRequest.Abort() is throwing an exception on a background/timer thread. This has nothing to do with HttpClient's Task management.

The exception from HttpWebRequest.Abort() should be fixed in .NET 4.5 GDR1. http://support.microsoft.com/kb/2750149 http://support.microsoft.com/kb/2750147

查看更多
太酷不给撩
3楼-- · 2019-03-08 14:46

Looks like it's some kind of bug in how the async handler for HttpClient is managing the tasks. I was able to launch the items in parallel, but run them synchronously and it works. I'm not sure if you want to just prevent the unhandled error or not. This ran parallel tasks, but they aren't async really since I turned it off. On my computer I would always get to 5 rounds and it would crash. Even if i set it to timeout after a second, it's like the crashes in the threads would still blow up if they were async.

I think it's a bug, I can't imagine this is intended behavior.

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();
        }
    }
}
查看更多
登录 后发表回答