背景
我有利用从一个特定的主机内容一批HTML页面处理一些代码。 它试图使同时使用HTTP请求的大量(〜400) HttpClient
。 我相信,同时连接的最大数目由限制ServicePointManager.DefaultConnectionLimit
,所以我不会将我自己的并发限制。
发送后,所有的请求的异步到HttpClient
使用Task.WhenAll
,整批的操作,可以取消CancellationTokenSource
和CancellationToken
。 的操作的进度经由用户接口是可见的,一个按钮可以被按下以执行消除。
问题
到呼叫CancellationTokenSource.Cancel()
30秒-为大致5个街区。 这将导致用户界面冻结。 的嫌疑,这是因为该方法被调用一个注册的取消通知的代码。
我已经考虑
- 限制的同时HTTP请求的任务数。 我认为这是一个工作,因为周围
HttpClient
似乎已经排队过剩请求本身。 - 执行
CancellationTokenSource.Cancel()
在非UI线程的方法调用。 这并没有工作也很好; 任务实际上并没有运行,直到其他大部分已经完成。 我认为,一个async
版本的方法,将工作做好,但我找不到一个。 另外,我的印象中,这是适合使用的方法,在UI线程。
示范
码
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();
}
}
产量
为什么取消块了这么久? 此外,有什么,我做错了还是可以做的更好?