【发布时间】:2013-02-17 12:44:52
【问题描述】:
背景
我有一些代码可以使用来自一个特定主机的内容执行批量 HTML 页面处理。它尝试使用HttpClient 发出大量(约400 个)并发HTTP 请求。我相信最大同时连接数受到ServicePointManager.DefaultConnectionLimit的限制,所以我没有应用我自己的并发限制。
使用Task.WhenAll将所有请求异步发送到HttpClient后,可以使用CancellationTokenSource和CancellationToken取消整个批处理操作。操作进度可通过用户界面查看,点击按钮即可取消操作。
问题
对CancellationTokenSource.Cancel() 的调用会阻塞大约 5 到 30 秒。这会导致用户界面冻结。怀疑出现这种情况是因为方法调用了注册取消通知的代码。
我的考虑
- 限制并发 HTTP 请求任务的数量。我认为这是一种解决方法,因为
HttpClient似乎已经将多余的请求本身排队。 - 在非 UI 线程中执行
CancellationTokenSource.Cancel()方法调用。这效果不太好。直到大多数其他任务完成后,该任务才真正运行。我认为该方法的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();
}
}
输出
为什么取消会阻止这么长时间?另外,我做错了什么或者可以做得更好吗?
【问题讨论】:
-
令人惊讶的是,我们遇到了相同的“瓶颈”并提出了几乎相同的问题:stackoverflow.com/questions/71278835/…
标签: c# performance .net-4.5 c#-5.0 cancellationtokensource