为什么取消了很多的HTTP请求时,确实消除块了这么久? [英] Why does cancellation block for so long when cancelling a lot of HTTP requests?

查看:304
本文介绍了为什么取消了很多的HTTP请求时,确实消除块了这么久?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景



我有使用从一个特定的主机内容的批处理HTML页面处理一些代码。它试图使使用的HttpClient 并发的HTTP请求的大量(〜400)。我相信,同时连接的最大数目由 ServicePointManager.DefaultConnectionLimit 的限制,所以我没有申请我自己的并发限制。



Task.WhenAll 使用发送所有的请求的异步到的HttpClient 之后,整批操作可使用 CancellationTokenSource 的CancellationToken 被取消。在操作的过程是通过用户界面查看,和一个按钮可以点击进行取消操作。



问题



CancellationTokenSource.Cancel()块调用的大约5 - 30秒。这将导致在用户界面冻结。是犯罪嫌疑人,这是因为该方法被调用一个注册取消通知的代码。



我已经考虑




  1. 限制的同时HTTP请求的任务数。我认为这是一个解决方法,因为的HttpClient 似乎已经排队多余的请求本身。

  2. 执行 CancellationTokenSource在非UI线程.Cancel()方法调用。这并没有工作也很好;任务实际上并没有运行,直到其他大部分已经完成。我认为方法的异步版本将工作得很好,但我无法找到一个。另外,我的印象中,这是适合使用的方法在UI线程。



演示



代码



 类节目
{
私人const int的desiredNumberOfConnections = 418;

静态无效的主要(字串[] args)
{
ManyHttpRequestsTest()等待();

Console.WriteLine(完了。);
Console.ReadKey();
}用

私有静态异步任务ManyHttpRequestsTest()
{
(VAR的客户=新的HttpClient())
使用(VAR cancellationTokenSource =新CancellationTokenSource ())
{
变种requestsCompleted = 0;使用

(VAR allRequestsStarted =新CountdownEvent(desiredNumberOfConnections))
{
行动reportRequestStarted =()=> allRequestsStarted.Signal();
行动reportRequestCompleted =()=> Interlocked.Increment(REF requestsCompleted);
Func键< INT,任务> getHttpResponse =指数=> GetHttpResponse(客户端,cancellationTokenSource.Token,reportRequestStarted,reportRequestCompleted);
VAR httpRequestTasks = Enumerable.Range(0,desiredNumberOfConnections)。选择(getHttpResponse);

Console.WriteLine(HTTP请求一批正在发起);
VAR httpRequestsTask = Task.WhenAll(httpRequestTasks);

Console.WriteLine(启动{0}请求({1})同时连接限制,desiredNumberOfConnections,ServicePointManager.DefaultConnectionLimit);
allRequestsStarted.Wait();

取消(cancellationTokenSource);
等待WaitForRequestsToFinish(httpRequestsTask);
}

Console.WriteLine({0} HTTP请求已经完成,requestsCompleted);
}
}

私有静态无效取消(CancellationTokenSource cancellationTokenSource)
{
Console.Write(取消...);

VAR秒表= Stopwatch.StartNew();
cancellationTokenSource.Cancel();
stopwatch.Stop();

Console.WriteLine(发生{0}秒,stopwatch.Elapsed.TotalSeconds);
}

私有静态异步任务WaitForRequestsToFinish(任务httpRequestsTask)
{
Console.WriteLine(等待HTTP请求完成);


{
等待httpRequestsTask;
}
赶上(OperationCanceledException)
{
Console.WriteLine(HTTP请求被取消);
}
}

私有静态异步任务GetHttpResponse(HttpClient的客户端的CancellationToken的CancellationToken,动作reportStarted,动作reportFinished)
{
VAR GETRESPONSE =客户端。 GetAsync(http://www.google.com的CancellationToken);

reportStarted();
使用(VAR响应=等待GETRESPONSE)
response.EnsureSuccessStatusCode();
reportFinished();
}
}



输出





为什么取消块了这么久?同时,有什么,我做错了,或可以做得更好?


解决方案

执行在非UI线程CancellationTokenSource.Cancel()方法调用。这并没有工作也很好;任务实际上并没有运行,直到其他大部分已经完成。




这告诉我的是,你可能患线程池枯竭',这是你的线程池的队列中有这么多项目(无法完成HTTP请求),这需要一段时间,通过他们全部搞定。取消可能阻止上执行的线程池的一些工作项目,它不能跳到队列的头。



这意味着你需要去选择从1你的考虑名单。油门你自己的工作,这样的线程池队列保持相对较短。这有利于应用程序的响应整体无妨。



我最喜欢的油门进行异步工作方式是使用的数据流。事情是这样的:

  VAR块=新ActionBlock<乌里>(
异步URI => {
VAR的HttpClient =新的HttpClient(); // HttpClient的是不是线程安全的,所以防止并发使用的专用实例为每个请求
VAR的结果=等待httpClient.GetAsync(URI);
//做更多的东西与结果
},
新ExecutionDataflowBlockOptions {MaxDegreeOfParallelism = 20,=的CancellationToken}的CancellationToken)。
的for(int i = 0; I< 1000;我++)
block.Post(新的URI(http://www.server.com/req+ i)段);
block.Complete();
等待block.Completion; //等待,直到一切都完成或取消。



作为替代方案,你可以使用Task.Factory.StartNew传递TaskCreationOptions.LongRunning让你的任务变一个的新的的线程(不隶属于线程池),这将使其能够立即开始,并呼吁从那里取消。但是,你也许应该解决的线程池枯竭问题吧。


Background

I have some code that performs batch HTML page processing using content from one specific host. It tries to make a large number (~400) of simultaneous HTTP requests using HttpClient. I believe that the maximum number of simultaneous connections is restricted by ServicePointManager.DefaultConnectionLimit, so I'm not applying my own concurrency restrictions.

After sending all of the requests asynchronously to HttpClient using Task.WhenAll, the entire batch operation can be cancelled using CancellationTokenSource and CancellationToken. The progress of the operation is viewable via a user interface, and a button can be clicked to perform the cancellation.

Problem

The call to CancellationTokenSource.Cancel() blocks for roughly 5 - 30 seconds. This causes the user interface to freeze. Is suspect that this occurs because the method is calling the code that registered for cancellation notification.

What I've Considered

  1. Limiting the number of simultaneous HTTP request tasks. I consider this a work-around because HttpClient already seems to queue excess requests itself.
  2. Performing the CancellationTokenSource.Cancel() method call in a non-UI thread. This didn't work too well; the task didn't actually run until most of the others had finished. I think an async version of the method would work well, but I couldn't find one. Also, I have the impression that it's suitable to use the method in a UI thread.

Demonstration

Code

class Program
{
    private const int desiredNumberOfConnections = 418;

    static void Main(string[] args)
    {
        ManyHttpRequestsTest().Wait();

        Console.WriteLine("Finished.");
        Console.ReadKey();
    }

    private static async Task ManyHttpRequestsTest()
    {
        using (var client = new HttpClient())
        using (var cancellationTokenSource = new CancellationTokenSource())
        {
            var requestsCompleted = 0;

            using (var allRequestsStarted = new CountdownEvent(desiredNumberOfConnections))
            {
                Action reportRequestStarted = () => allRequestsStarted.Signal();
                Action reportRequestCompleted = () => Interlocked.Increment(ref requestsCompleted);
                Func<int, Task> getHttpResponse = index => GetHttpResponse(client, cancellationTokenSource.Token, reportRequestStarted, reportRequestCompleted);
                var httpRequestTasks = Enumerable.Range(0, desiredNumberOfConnections).Select(getHttpResponse);

                Console.WriteLine("HTTP requests batch being initiated");
                var httpRequestsTask = Task.WhenAll(httpRequestTasks);

                Console.WriteLine("Starting {0} requests (simultaneous connection limit of {1})", desiredNumberOfConnections, ServicePointManager.DefaultConnectionLimit);
                allRequestsStarted.Wait();

                Cancel(cancellationTokenSource);
                await WaitForRequestsToFinish(httpRequestsTask);
            }

            Console.WriteLine("{0} HTTP requests were completed", requestsCompleted);
        }
    }

    private static void Cancel(CancellationTokenSource cancellationTokenSource)
    {
        Console.Write("Cancelling...");

        var stopwatch = Stopwatch.StartNew();
        cancellationTokenSource.Cancel();
        stopwatch.Stop();

        Console.WriteLine("took {0} seconds", stopwatch.Elapsed.TotalSeconds);
    }

    private static async Task WaitForRequestsToFinish(Task httpRequestsTask)
    {
        Console.WriteLine("Waiting for HTTP requests to finish");

        try
        {
            await httpRequestsTask;
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("HTTP requests were cancelled");
        }
    }

    private static async Task GetHttpResponse(HttpClient client, CancellationToken cancellationToken, Action reportStarted, Action reportFinished)
    {
        var getResponse = client.GetAsync("http://www.google.com", cancellationToken);

        reportStarted();
        using (var response = await getResponse)
            response.EnsureSuccessStatusCode();
        reportFinished();
    }
}

Output

Why does cancellation block for so long? Also, is there anything that I'm doing wrong or could be doing better?

解决方案

Performing the CancellationTokenSource.Cancel() method call in a non-UI thread. This didn't work too well; the task didn't actually run until most of the others had finished.

What this tells me is that you're probably suffering from 'threadpool exhaustion', which is where your threadpool queue has so many items in it (from HTTP requests completing) that it takes a while to get through them all. Cancellation probably is blocking on some threadpool work item executing and it can't skip to the head of the queue.

This suggests that you do need to go with option 1 from your consideration list. Throttle your own work so that the threadpool queue remains relatively short. This is good for app responsiveness overall anyway.

My favorite way to throttle async work is to use Dataflow. Something like this:

var block = new ActionBlock<Uri>(
    async uri => {
        var httpClient = new HttpClient(); // HttpClient isn't thread-safe, so protect against concurrency by using a dedicated instance for each request.
        var result = await httpClient.GetAsync(uri);
        // do more stuff with result.
    },
    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 20, CancellationToken = cancellationToken });
for (int i = 0; i < 1000; i++)
    block.Post(new Uri("http://www.server.com/req" + i));
block.Complete();
await block.Completion; // waits until everything is done or canceled.

As an alternative, you could use Task.Factory.StartNew passing in TaskCreationOptions.LongRunning so your task gets a new thread (not affiliated with threadpool) which would allow it to start immediately and call Cancel from there. But you should probably solve the threadpool exhaustion problem instead.

这篇关于为什么取消了很多的HTTP请求时,确实消除块了这么久?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆