Why task cancellation takes so long in Flurl.Http DownloadFileAsync / HttpClient GetAsync(为什么Flurl.Http DownloadFileAsync/Http客户端GetAsync需要这么长时间才能取消任务)
问题描述
在编写从服务器并行下载图像的类时(使用由DataFlow TPL库支持的消费者/生产者模式),使用ActionBlock
和Flurl.Http
工具方法DownloadFileAsync
,我已经意识到取消操作需要很长时间。由于所有下载操作都共享一个CancellationToken
,我预计所有任务都会立即(或几乎)取消。实际上,如果我产生大量并行下载,取消所有任务/线程可能需要几分钟时间。我已经解决了这个问题,将ExecutionDataflowBlockOptions.MaxDegreeOfParallelism
属性设置为10。这样,在任何给定时间最多有10个要取消的并发下载(这仍然不是我预期的立即操作)。
我已经创建了一个控制台.NET5程序,该程序隔离地重现该问题(没有数据流TPL、ActionBlock
等)。它首先询问并发下载的数量(默认情况下按Enter键:100次下载)。然后,它使用Flurl.Http
(使用HttpClient
)并行生成所有这些下载,并向每个操作传递一个CancellationToken
。然后等待按键,然后通过调用CancellationTokenSource.Cancel
方法取消挂起的下载。最后,它会打印一些统计数据,包括成功和失败/取消的下载次数,以及完成取消所需的时间。以下是完整的代码:
最让我印象深刻的是,允许所有下载完成(即使我输入了1000次下载)比取消操作更快。我不知道是否存在任何类型的死锁(这会导致操作在锁定超时后结束),或者这些下载的取消是否只是中断。我找不到对此问题的好解释或解决方案。要重现该问题,您必须在所有下载完成之前按某个键以取消挂起的下载。如果你选择了正确的时机,你可以让几次下载成功。如果你点击太快,你会取消所有下载。如果您等待的时间太长,则所有下载将已完成。
此运行导致以下结果:
取消99个挂起的操作花费了55秒以上的时间。如果我只允许完成所有下载,则所需时间比取消相同操作所需的时间要少得多。更新
我已经完全删除了Flurl,并直接使用HttpClient下载文件,但问题仍然存在。我已将Downlod方法更改为以下内容:
结果与基于FLURL的实现相同(毕竟,Flurl.Http只是HttpClient的包装)。
更新2
我已经将下载方法更改为简单地等待可取消的Task.Delay
,100个操作的取消时间现在约为2秒。虽然它更快,但它不是即时的,从屏幕上日志的计时来看,在我看来,取消是顺序触发的,而不是并行/立即触发的。此下载的代码为:
以下屏幕截图显示了上述代码的结果:
有人对此有好的解释或解决方案吗?
推荐答案
如果您将取消令牌单独传递给任务而不是父任务Task.WaitAll()
,则在您调用cts.Cancel()
之后,其他任务将不知道取消请求,直到为每个任务调用Download()
。
将令牌传递给父级,使其充当所有任务的协调器:
然后,一旦触发取消,将不需要执行剩余的任务,从而为您节省取消时间。
请注意,取消时间要短得多,因为我们只需要取消当时正在执行的任务,而不是所有任务都会自己收到取消的通知。我们完成了70项任务,其中4项被取消,其余的刚刚放弃,取消时间大大缩短。这篇关于为什么Flurl.Http DownloadFileAsync/Http客户端GetAsync需要这么长时间才能取消任务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!