任务并行库异常处理

5
处理TPL任务中的异常时,我遇到了两种处理异常的方式。第一种在任务内部捕获异常,并像下面这样在结果中返回它:
var task = Task<Exception>.Factory.StartNew(
    () =>
        {
            try
            {
                // Do Something

                return null;
            }
            catch (System.Exception e)
            {
                return e;
            }
        });

task.ContinueWith(
    r =>
        {
            if (r.Result != null)
            {
                // Handle Exception
            }
        });

第二种方法是文档中显示的方式,我认为这是正确的做法:
var task = Task.Factory.StartNew(
    () =>
        {
            // Do Something
        });
task.ContinueWith(
    r =>
        {
            if (r.Exception != null)
            {
                // Handle Aggregate Exception
                r.Exception.Handle(y => true);
            }
        });

我在想第一种方法是否有问题?使用此技术时,我偶尔会收到“未处理聚合异常”异常,并想知道如何发生这种情况?为了澄清,我认为第二种模式更好,但我有一段代码使用第一种模式,并且我正在尝试找出它是否需要重新设计,即如果不是所有异常都能被捕获。

我曾经遇到过同样的问题,虽然我通过 task.IsFaulted 进行了检查,但如果任务在执行期间出现异常,即使我第一时间就检查并记录下来并放弃任务,仍会导致问题。我还将未处理的异常泄漏出去了,这是不合适的。 - BugFinder
1个回答

3
第一种方法假设每次调用都会引发异常。虽然这可能是真的,但异常似乎并不“异常”,并且存在设计问题。如果异常并不是异常情况,那么结果就没有太多意义。另一个问题是,如果您想要一个“结果”(即除Exception之外的其他内容),则无法获得,因为唯一的Result插槽用于Exception。另一个问题是,您不会在主线程上重新抛出异常(可以手动执行此操作),因此您不会获得catch语义(即您正在使用Handle方法)。
第二种方法将被更多人理解。

感谢您的评论。我正在一个需要异常处理(数据库访问)的存储库中使用这种方法。如果我需要返回结果,我会使用RepositoryResult类,该类可以由任务返回,并包含结果和异常。任务继续是异常处理发生的地方(即调用异常处理服务)。我真正想知道的是,如何使用第一种方法最终导致未处理的聚合异常? - user1680766
因此,处理异常的方式是不一致的。TaskTask<T>由于缺少Result属性而没有一致的方式,但已经有了Task.Exception属性以及IsFaulted... - Peter Ritchie
我同意,第二种方法更好。不幸的是,我接手了一些大量使用第一种模式的代码,现在我需要决定是否有必要回头重构它 - 因此问题是第一种模式是否会导致未处理的聚合异常。 - user1680766
我认为第二种方法更易于维护;但是,如果其他代码能够正常工作,可能没有重构的必要。 - Peter Ritchie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接