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
块可以保证我的信号量和其他可支配资源得到正确释放,从而为与取消和网络错误相关的错误留下很小的空间。
但是它应该如何完成,还是可以在不牺牲可读性和简单性的情况下进行优化?
没有代码示例,因为它会很大。简单的例子要么花费我很长时间来编写(我现在没有),要么太简单,无法观察异常通过代码传播时会发生什么。