C# 当取消大量HTTP请求时,为什么取消会阻塞这么长时间? 背景
我有一些代码,它使用来自一个特定主机的内容执行批量HTML页面处理。它尝试使用C# 当取消大量HTTP请求时,为什么取消会阻塞这么长时间? 背景,c#,performance,.net-4.5,c#-5.0,cancellationtokensource,C#,Performance,.net 4.5,C# 5.0,Cancellationtokensource,我有一些代码,它使用来自一个特定主机的内容执行批量HTML页面处理。它尝试使用HttpClient同时发出大量(~400)HTTP请求。我认为同时连接的最大数量受到ServicePointManager.DefaultConnectionLimit的限制,因此我没有应用自己的并发限制 使用Task将所有请求异步发送到HttpClient后。所有时,可以使用CancellationTokenSource和CancellationToken取消整个批处理操作。可通过用户界面查看操作的进度,并可单击按
HttpClient
同时发出大量(~400)HTTP请求。我认为同时连接的最大数量受到ServicePointManager.DefaultConnectionLimit
的限制,因此我没有应用自己的并发限制
使用Task将所有请求异步发送到HttpClient
后。所有时,可以使用CancellationTokenSource
和CancellationToken
取消整个批处理操作。可通过用户界面查看操作的进度,并可单击按钮执行取消操作
问题
调用CancellationTokenSource.Cancel()
会阻塞大约5-30秒。这会导致用户界面冻结。怀疑发生这种情况是因为该方法正在调用为取消通知注册的代码
我所考虑的
限制同步HTTP请求任务的数量。我认为这是一个工作,因为<代码> HTTPcli客< /代码>似乎已经对多余的请求本身进行了排队。
在非UI线程中执行CancellationTokenSource.Cancel()
方法调用。这不太管用;直到其他大多数人都完成了任务,任务才真正开始运行。我认为该方法的async
版本可以很好地工作,但我找不到。另外,我觉得在UI线程中使用该方法是合适的
示范
代码
类程序
{
private const int desiredNumberOfConnections=418;
静态void Main(字符串[]参数)
{
许多前传()等待();
控制台。WriteLine(“完成”);
Console.ReadKey();
}
私有静态异步任务manyhtprequeststest()
{
使用(var client=new HttpClient())
使用(var cancellationTokenSource=new cancellationTokenSource())
{
var requestsCompleted=0;
使用(var allrequestssstarted=newcountdownEvent(desiredNumberOfConnections))
{
Action reportRequestStarted=()=>allRequestsStarted.Signal();
动作报告RequestCompleted=()=>Interlocked.Increment(ref requestsCompleted);
Func getHttpResponse=index=>getHttpResponse(客户端,cancellationTokenSource.Token,reportRequestStarted,reportRequestCompleted);
var httpRequestTasks=Enumerable.Range(0,desiredNumberOfConnections)。选择(getHttpResponse);
WriteLine(“HTTP请求批处理正在启动”);
var httpRequestsTask=Task.WhenAll(httpRequestTasks);
WriteLine(“启动{0}个请求(同时连接限制为{1})”,desiredNumberOfConnections,ServicePointManager.DefaultConnectionLimit);
allRequestsStarted.Wait();
取消(cancellationTokenSource);
等待WaitForRequestsToFinish(httpRequestsTask);
}
WriteLine(“{0}HTTP请求已完成”,requestsCompleted);
}
}
私有静态无效取消(CancellationTokenSource CancellationTokenSource)
{
控制台。写入(“取消…”);
var stopwatch=stopwatch.StartNew();
cancellationTokenSource.Cancel();
秒表;
WriteLine(“花费了{0}秒”,秒表.appead.TotalSeconds);
}
私有静态异步任务WaitForRequestsToFinish(任务httpRequestsTask)
{
WriteLine(“等待HTTP请求完成”);
尝试
{
等待httpRequestsTask;
}
捕获(操作取消异常)
{
WriteLine(“HTTP请求被取消”);
}
}
私有静态异步任务GetHttpResponse(HttpClient客户端、CancellationToken CancellationToken、Action reportStarted、Action reportFinished)
{
var getResponse=client.GetAsync(“http://www.google.com“,取消令牌);
reportStarted();
使用(var响应=等待getResponse)
response.EnsureSuccessStatusCode();
reportFinished();
}
}
输出
为什么取消会阻塞这么长时间?还有,我有没有做错什么,或者可以做得更好
在非UI线程中执行CancellationTokenSource.Cancel()方法调用。这不太管用;直到其他大多数人都完成了任务,任务才真正开始运行
这告诉我的是,您可能正在遭受“线程池耗尽”的痛苦,这就是线程池队列中有太多项目(来自HTTP请求完成)的原因,需要一段时间才能全部完成。取消操作可能会阻塞某些线程池工作项的执行,并且不能跳到队列的头部
这表明您确实需要从考虑事项列表中选择选项1。限制您自己的工作,使线程池队列保持相对较短。无论如何,这对应用程序的整体响应性是有好处的
我最喜欢的控制异步工作的方法是使用。大概是这样的:
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.
var block=新动作块(
异步uri=>{
var httpClient=new httpClient();//httpClient不是线程安全的,因此通过为每个请求使用专用实例来防止并发。
var result=await-httpClient.GetAsync(uri);
//用结果做更多的事情。
},
新的ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=20,CancellationToken=CancellationToken});
对于(int i=0;i<1000;i++)
block.Post(新Uri(“http://www.server.com/req“+i”);
block.Complete();
等待区块完成;//等待所有操作完成或取消。
作为替代方案,您可以使用Task.fact
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.