Ustawienie HttpClient na zbyt krótki timeout powoduje awarię procesu

Zauważyłem, że kiedy używam System.Net.HttpClient z krótkim czasem oczekiwania, może to czasami spowodować awarię procesu, nawet jeśli jest on zawinięty w blok try-catch. Oto krótki program do odtworzenia tego.

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

Uruchomienie tego spowoduje awarię procesu z następującym wyjątkiem:

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

Grzebanie w trochę, wydaje się, że gdy HttpClient przerywa żądanie przed utworzeniem odpowiedniego ServicePoint, HttpWebRequest próbuje utworzyć ServicePoint, poprzez ServicePointManager.FindServicePoint, który rzuca RequestCanceled. Ponieważ ten wyjątek jest wrzucany do wątku, który próbuje anulować żądanie, nie jest przechwytywany, a proces umiera.

Czy coś przeoczyłem? Spotkałeś się z tym problemem?

Author: Doron Yaacoby, 2013-03-12

2 answers

HttpWebRequest.Abort() rzuca wyjątek na wątek tła/timera. Nie ma to nic wspólnego z zarządzaniem zadaniami HttpClient.

Wyjątek od HttpWebRequest.Abort() powinien być naprawiony w. NET 4.5 GDR1. http://support.microsoft.com/kb/2750149 http://support.microsoft.com/kb/2750147

 20
Author: Tratcher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-18 17:45:05

Wygląda na to, że jest to jakiś błąd w tym, jak asynchroniczna obsługa HttpClient zarządza zadaniami. Udało mi się uruchomić elementy równolegle, ale uruchomić je synchronicznie i działa. Nie jestem pewien, czy chcesz po prostu zapobiec nieobsługiwanym błędom, czy nie. To uruchamiało równoległe zadania, ale nie są one asynchroniczne, odkąd go wyłączyłem. Na moim komputerze zawsze dostaję 5 rund i się rozbija. Nawet jeśli ustawię to na timeout po sekundzie, to tak jakby awarie w wątkach nadal wybuchały w górę, gdyby były asynchroniczne.

Myślę, że to błąd, nie wyobrażam sobie, żeby to było zamierzone zachowanie.

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();
        }
    }
}
 4
Author: Roy Ashbrook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-18 17:24:12