如何在使用ContinueWith()时获得原始异常



考虑以下代码:

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
    static class Program
    {
        static void Main()
        {
            var tasks = new Task[1];
            tasks[0] = Task.Run(() => throwExceptionAfterOneSecond())
                .ContinueWith(task => {
                    Console.WriteLine("ContinueWith()"); }, TaskContinuationOptions.NotOnFaulted);
            try
            {
                Task.WaitAll(tasks);
            }
            catch (AggregateException ex)
            {
                Console.WriteLine("Exception received: " + ex.InnerExceptions.Single().Message);
            }
        }
        static void throwExceptionAfterOneSecond()
        {
            Thread.Sleep(1000);
            throw new InvalidOperationException("TEST");
        }
    }
}

这会产生以下输出:

Exception received: A task was canceled.

我的问题很简单:如何获得原始InvalidOperationException("TEST");而不是System.Threading.Tasks.TaskCanceledException

请注意,如果删除.ContinueWith()部分,这将如我所期望的那样工作,并且在这种情况下的输出是Exception received: TEST

(还要注意,此示例使用.Net 4.5,但原始代码必须使用.Net 4.0)


解决方案

多亏有了答案,这一切现在都开始奏效了。我选择了以下解决方案-我需要等待原始任务和继续任务:

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
    static class Program
    {
        static void Main()
        {
            var tasks = new Task[2];
            tasks[0] = Task.Run(() => throwExceptionAfterOneSecond());
            tasks[1] = tasks[0].ContinueWith(task => {
                if (task.Status == TaskStatus.RanToCompletion)
                    Console.WriteLine("ContinueWith()"); });
            try
            {
                Task.WaitAll(tasks);
            }
            catch (AggregateException ex)
            {
                Console.WriteLine("Exception received: " + ex.InnerExceptions.Single().Message);
            }
            Console.WriteLine("Done.");
        }
        static void throwExceptionAfterOneSecond()
        {
            Thread.Sleep(1000);
            throw new InvalidOperationException("TEST");
        }
    }
}

您需要存储对Task.Run(() => throwExceptionAfterOneSecond())的引用,以便稍后检查它的Exception属性。这是唯一出现故障的任务。检查任何其他任务都不会提供该异常。

我也不会依赖TaskContinuationOptions.NotOnFaulted,因为这在很大程度上迫使控制流使用异常。等待一个非正常完成的任务而不抛出异常是很困难的。

.ContinueWith(task => {
     if (task.Status == RanToCompletion) Console.WriteLine("ContinueWith()");
}

如果需要捕获catch语句中的异常,可以从continue内部重新抛出。例如

tasks[0] = Task.Run(() => throwExceptionAfterOneSecond())
    .ContinueWith(task =>
    {
        if (task.IsFaulted)
        {
            // Throw the inner exception
            throw task.Exception.InnerException;
        }
        Console.WriteLine("ContinueWith()");
    });

您可以将ContinueWith部分拆分为这样一种方式,即它们在异常情况下是分开的,在成功情况下是分离的。这里有一个例子:

var tasks = new Task[1];
tasks[0] = Task.Run(() => throwExceptionAfterOneSecond());
// For error handling.
tasks[0].ContinueWith(task =>
    {
        // Your logic to handle the exception goes here
        // Aggregate exception
        Console.WriteLine(task.Exception.Message);
        // Inner exception, which is your custom exception
        Console.WriteLine(task.Exception.InnerException.Message);
    },
    TaskContinuationOptions.OnlyOnFaulted);
// If it succeeded.
tasks[0].ContinueWith(task => 
{
    // success
    Console.WriteLine("ContinueWith()");
},TaskContinuationOptions.OnlyOnRanToCompletion);

最新更新