HttpClient的异步请求未完成对大批量送出在一个循环送出、未完成、HttpClient

2023-09-03 01:36:44 作者:不打扰丶是我最后的温柔

我觉得我已经成功地进行测试,显示这个问题的可重复的,至少在我的系统。 这个问题涉及到的HttpClient被用于一个坏的端点(不存在的端点,目标是下降)。

I think I've managed to make a test that shows this problem repeatably, at least on my system. This question relates to HttpClient being used for a bad endpoint (nonexistant endpoint, the target is down).

的问题是,已完成任务的数不足的总的,通常由约几。我不介意不工作的要求,但这只是导致应用程序只是挂在那里当结果被期待已久的。

The problem is that the number of completed tasks falls short of the total, usually by about a few. I don't mind requests not working, but this just results in the app just hanging there when the results are awaited.

我得到以下结果表格下面的测试code:

I get the following result form the test code below:

播放时间:237.2009884秒。 任务批量阵:8000完成的任务:7993 的

Elapsed: 237.2009884 seconds. Tasks in batch array: 8000 Completed Tasks : 7993

如果我设置BATCHSIZE为8,而不是8000,它完成。对于8000这拥堵的WhenAll。

If i set batchsize to 8 instead of 8000, it completes. For 8000 it jams on the WhenAll .

我不知道其他人得到相同的结果,如果我做错了什么,如果这似乎是一个错误。

I wonder if other people get the same result, if I am doing something wrong, and if this appears to be a bug.

using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace CustomArrayTesting
{

    /// <summary>
    /// Problem: a large batch of async http requests is done in a loop using HttpClient, and a few of them never complete
    /// </summary>
    class ProgramTestHttpClient
    {
        static readonly int batchSize = 8000; //large batch size brings about the problem

        static readonly Uri Target = new Uri("http://localhost:8080/BadAddress");

        static TimeSpan httpClientTimeout = TimeSpan.FromSeconds(3);  // short Timeout seems to bring about the problem.

        /// <summary>
        /// Sends off a bunch of async httpRequests using a loop, and then waits for the batch of requests to finish.
        /// I installed asp.net web api client libraries Nuget package.
        /// </summary>
        static void Main(String[] args)
        {
            httpClient.Timeout = httpClientTimeout; 

            stopWatch = new Stopwatch();
            stopWatch.Start();


            // this timer updates the screen with the number of completed tasks in the batch (See timerAction method bellow Main)
            TimerCallback _timerAction = timerAction;
            TimerCallback _resetTimer = ResetTimer;
            TimerCallback _timerCallback = _timerAction + _resetTimer;

            timer = new Timer(_timerCallback, null, TimeSpan.FromSeconds(1), Timeout.InfiniteTimeSpan);
            //

            for (int i = 0; i < batchSize; i++)
            {
                Task<HttpResponseMessage> _response = httpClient.PostAsJsonAsync<Object>(Target, new Object());//WatchRequestBody()

                Batch[i] = _response;
            }

            try
            {
                Task.WhenAll(Batch).Wait();
            }
            catch (Exception ex)
            {

            }

            timer.Dispose();
            timerAction(null);
            stopWatch.Stop();


            Console.WriteLine("Done");
            Console.ReadLine();
        }

        static readonly TimeSpan timerRepeat = TimeSpan.FromSeconds(1);

        static readonly HttpClient httpClient = new HttpClient();

        static Stopwatch stopWatch;

        static System.Threading.Timer timer;

        static readonly Task[] Batch = new Task[batchSize];

        static void timerAction(Object state)
        {
            Console.Clear();
            Console.WriteLine("Elapsed: {0} seconds.", stopWatch.Elapsed.TotalSeconds);
            var _tasks = from _task in Batch where _task != null select _task;
            int _tasksCount = _tasks.Count();

            var _completedTasks = from __task in _tasks where __task.IsCompleted select __task;
            int _completedTasksCount = _completedTasks.Count();

            Console.WriteLine("Tasks in batch array: {0}       Completed Tasks : {1} ", _tasksCount, _completedTasksCount);

        }

        static void ResetTimer(Object state)
        {
            timer.Change(timerRepeat, Timeout.InfiniteTimeSpan);
        }
    }
}

有时候访问冲突未处理的异常完成前刚刚崩溃。调用堆栈只是说:

Sometimes it just crashes before finishing with an Access Violation unhandled exception. The call stack just says:

>   mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode = 1225, uint numBytes = 0, System.Threading.NativeOverlapped* pOVERLAP = 0x08b38b98) 
    [Native to Managed Transition]  
    kernel32.dll!@BaseThreadInitThunk@12()  
    ntdll.dll!___RtlUserThreadStart@8()     
    ntdll.dll!__RtlUserThreadStart@8()  

在大多数情况下它不会崩溃,但只是从未完成守候在whenall。在任何情况下,下面的第一个机会异常抛出的每一个请求:

Most of the time it doesn't crash but just never finishes waiting on the whenall. In any case the following first chance exceptions are thrown for each request:

A first chance exception of type 'System.Net.Sockets.SocketException' occurred in System.dll
A first chance exception of type 'System.Net.WebException' occurred in System.dll
A first chance exception of type 'System.AggregateException' occurred in mscorlib.dll
A first chance exception of type 'System.ObjectDisposedException' occurred in System.dll

我做的处理对象的异常调试器停止,并得到这个调用堆栈:

I made the debugger stop on the Object disposed exception, and got this call stack:

>   System.dll!System.Net.Sockets.NetworkStream.UnsafeBeginWrite(byte[] buffer, int offset, int size, System.AsyncCallback callback, object state) + 0x136 bytes    
    System.dll!System.Net.PooledStream.UnsafeBeginWrite(byte[] buffer, int offset, int size, System.AsyncCallback callback, object state) + 0x19 bytes  
    System.dll!System.Net.ConnectStream.WriteHeaders(bool async = true) + 0x105 bytes   
    System.dll!System.Net.HttpWebRequest.EndSubmitRequest() + 0x8a bytes    
    System.dll!System.Net.HttpWebRequest.SetRequestSubmitDone(System.Net.ConnectStream submitStream) + 0x11d bytes  
    System.dll!System.Net.Connection.CompleteConnection(bool async, System.Net.HttpWebRequest request = {System.Net.HttpWebRequest}) + 0x16c bytes  
    System.dll!System.Net.Connection.CompleteConnectionWrapper(object request, object state) + 0x4e bytes   
    System.dll!System.Net.PooledStream.ConnectionCallback(object owningObject, System.Exception e, System.Net.Sockets.Socket socket, System.Net.IPAddress address) + 0xf0 bytes 
    System.dll!System.Net.ServicePoint.ConnectSocketCallback(System.IAsyncResult asyncResult) + 0xe6 bytes  
    System.dll!System.Net.LazyAsyncResult.Complete(System.IntPtr userToken) + 0x65 bytes    
    System.dll!System.Net.ContextAwareResult.Complete(System.IntPtr userToken) + 0x92 bytes 
    System.dll!System.Net.LazyAsyncResult.ProtectedInvokeCallback(object result, System.IntPtr userToken) + 0xa6 bytes  
    System.dll!System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped) + 0x98 bytes 
    mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) + 0x6e bytes    
    [Native to Managed Transition]

异常消息是:

The exception message was:

{"Cannot access a disposed object.\r\nObject name: 'System.Net.Sockets.NetworkStream'."}    System.Exception {System.ObjectDisposedException}

请注意,关系到未处理的访问冲突异常,我很少看到。

Notice the relationship to that unhandled access violation exception that I rarely see.

因此​​,似乎HttpClient的不是当目标是向下健壮。我在Windows 7 32位的方式这样做。

So, it seems that HttpClient is not robust for when the target is down. I am doing this on windows 7 32 by the way.

推荐答案

我用反射镜看了看,通过HttpClient的来源。对于操作(当它被踢客)的同步执行的部分,似乎有没有超时应用到返回的任务,据我所看到的。有一些超时的实现,一个HttpWebRequest对象呼吁中止(),但同样他们似乎已经错过了任何超时取消返回的任务或断层上的异步函数的这一边。也许还有一些在回调的一面,但有时回调可能是失踪,导致返回的任务从未完成。

I looked through the source of HttpClient using reflector. For the synchronously executed part of the operation (when it is kicked-off), there seems to be no timeout applied to the returned task, as far as I can see. There is some timeout implementation that calls Abort() on an HttpWebRequest object, but again they seem to have missed out any timeout cancellation or faulting of the returned task on this side of the async function. There maybe something on the callback side, but sometimes the callback is probably "going missing", leading to the returned Task never completing.

我发布了一个问题,询问如何超时添加到任何任务,而回答者给了这个非常好的解决方案(这里作为一个扩展的方法):

I posted a question asking how to add a timeout to any Task, and an answerer gave this very nice solution (here as an extension method):

public static Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
{
    var delay = task.ContinueWith(t => t.Result
        , new CancellationTokenSource(timeout).Token);
    return Task.WhenAny(task, delay).Unwrap();
}

所以,调用HttpClient的这样应该prevent任何来自永不落幕的任务坏了

So, calling HttpClient like this should prevent any "Tasks gone bad" from never ending:

Task<HttpResponseMessage> _response = httpClient.PostAsJsonAsync<Object>(Target, new Object()).WithTimeout<HttpResponseMessage>(httpClient.Timeout);

一对夫妇更多的事情,我想提出的要求不太可能失踪: 1.增加超时从3秒到30秒作出的各项任务中,我贴这个有问题的程序完成。 2.增加并发连接数,可以允许使用例如System.Net.ServicePointManager.DefaultConnectionLimit = 100;

A couple more things that I think made requests less likely to go missing: 1. Increasing the timeout from 3s to 30s made all the tasks finish in the program that I posted with this question. 2. Increasing the number of concurrent connections allowed using for example System.Net.ServicePointManager.DefaultConnectionLimit = 100;