使用.NET中的异常取消多个并行复杂操作的成本是多少?

时间:2016-11-02 23:29:54

标签: c# .net windows multithreading

TL; DR:异常可以取消最里面的呼叫吗?我希望我的代码既可维护又快速。但可维护性是最重要的。

在我的第一种方法中,我尽可能地避免了基于异常的取消。这导致完全无法读取的代码有很多错误,它几乎是随机出现的。我无法解决它,我只是从一开始就使用基于异常的方法重写它。现在它起作用,我的工作已经完成,但我想了解更多,所以......

问题如下。我有一个程序,一次下载数千个小文件,n个文件(同时)。

文件按批次组织,一批包含大约一千个文件。完成整批后,我会更新一些元数据,以便应用程序知道哪些批次是完整的,它们的结构如何看,哪些文件已经下载(校验和)等等。

我当然可以点击(开始下载)尽可能多的批次,但我有一个只允许n并发下载的信号量,所以没有什么不好的事情,所有内容都以最快的方式下载。

现在我有边缘情况:应用程序在下载期间突然关闭,特定批次被取消或 - 下载期间发生网络错误。

在每个边缘情况下,我需要立即停止所有下载任务,编写所有计算的校验和和其他应用程序状态,这样用户就不需要重新下载文件了。

我介绍了两个CancellationToken,一个全局,在应用程序OnExit覆盖中取消,第二个本地,每批,用于取消特定批量下载。然后使用CancellationTokenSource.CreateLinkedTokenSource()组合它们以使相关代码更简单。

批处理下载过程相当复杂且异步,让我们说它在LoadAsync()方法中。此方法调用许多其他方法,创建下载任务列表,重点是 - 每个依赖异步方法都采用组合令牌,并且可以在多个点中取消。当然HttpClient也会收到取消令牌。

我观察到取消事件发生时,HttpClient会抛出异常,OpeationCanceledException类型。

因此,在最终得到异常的循环中,我有适当的try / catch / finally块来处理它们。关键点是异常通过几个方法调用传播,以便被最外层的方法捕获。

现在它变得有趣了:当我使用Visual Studio调试运行代码时,取消过程会导致一个巨大的口吃,最多1个CPU核心负载并持续几秒钟。这是因为VS存储了每个异常的调用堆栈和其他元数据。假设有成千上万的。

在没有调试的情况下运行 - 取消是即时的,效果立竿见影。在少于1个GUI框架中取消了所有数千个下载。我点击取消的那一刻停止与我取消1个文件下载完全相同。

我的问题是 - 当异常通过代码传播时会发生什么?这是取消多个复杂并行任务的最佳方法,还是有更快的替代方法来改变我的代码流?

我在代码中经常使用token.ThrowIfCancellationRequested()。好吧 - 似乎微软在HttpClient中仍然使用它,所以生成的代码非常一致,整洁可读。 Finally块可以保证我的信号量和其他可支配资源得到正确释放,从而为与取消和网络错误相关的错误留下很小的空间。

但是它应该如何完成,还是可以在不牺牲可读性和简单性的情况下进行优化?

没有代码示例,因为它会很大。简单的例子要么花费我很长时间来编写(我现在没有),要么太简单,无法观察异常通过代码传播时会发生什么。

0 个答案:

没有答案