在任务中继续处理异常的正确方法

时间:2014-02-03 06:15:54

标签: c# .net task-parallel-library task

请查看以下代码 -

static void Main(string[] args)
{
    // Get the task.
    var task = Task.Factory.StartNew<int>(() => { return div(32, 0); });

    // For error handling.
    task.ContinueWith(t => { Console.WriteLine(t.Exception.Message); }, 
        TaskContinuationOptions.OnlyOnFaulted);

    // If it succeeded.
    task.ContinueWith(t => { Console.WriteLine(t.Result); }, 
        TaskContinuationOptions.OnlyOnRanToCompletion);
    Console.ReadKey();
    Console.WriteLine("Hello");
}

private static int div(int x, int y)
{
    if (y == 0)
    {
        throw new ArgumentException("y");
    }
    return x / y;
}

如果我在发布模式下执行代码,输出为“发生了一个或多个错误”,一旦我点击“Enter”键,“Hello”也会显示。如果我在调试模式下运行代码,输出与发布模式相同。但在IDE中调试时,控件执行行时会出现IDE异常消息(“用户代码中未处理的异常”)

throw new ArgumentException("y"); 

如果我从那里继续,程序不会崩溃并显示与发布模式相同的输出。这是处理异常的正确方法吗?

6 个答案:

答案 0 :(得分:63)

您可能不需要单独的OnlyOnFaultedOnlyOnRanToCompletion处理程序,并且您没有处理OnlyOnCanceled。查看this answer了解更多详情。

  

但是在IDE中调试时,会出现一条IDE异常消息(“未处理   当控件执行行

时,会出现用户代码“)中的异常

您在调试器下看到异常,因为您可能已在Debug / Exceptions选项中启用它( Ctrl + Alt + E )。< / p>

  

如果我从那里继续,程序不会崩溃并显示   与发布模式相同的输出。这是正确的处理方式吗?   异常?

Task操作中抛出但未处理的异常不会自动重新抛出。相反,它将被包装以供将来观察Task.Exception(类型AggregateException)。您可以访问原始例外Exception.InnerException

Exception ex = task.Exception;
if (ex != null && ex.InnerException != null)
    ex = ex.InnerException;

要在这种情况下使程序崩溃,您实际上需要观察异常外部任务操作,例如通过引用Task.Result

static void Main(string[] args)
{
    // Get the task.
    var task = Task.Factory.StartNew<int>(() => { return div(32, 0); });

    // For error handling.
    task.ContinueWith(t => { Console.WriteLine(t.Exception.Message); }, 
        TaskContinuationOptions.OnlyOnFaulted);

    // If it succeeded.
    task.ContinueWith(t => { Console.WriteLine(t.Result); }, 
        TaskContinuationOptions.OnlyOnRanToCompletion);

    Console.ReadKey();

    Console.WriteLine("result: " + task.Result); // will crash here

    // you can also check task.Exception

    Console.WriteLine("Hello");
}

更多详情:Tasks and Unhandled ExceptionsTask Exception Handling in .NET 4.5

更新以解决评论:以下是我在使用.NET 4.0和VS2010的UI应用中执行此操作的方法:

void Button_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => 
    {
        return div(32, 0); 
    }).ContinueWith((t) =>
    {
        if (t.IsFaulted)
        {
            // faulted with exception
            Exception ex = t.Exception;
            while (ex is AggregateException && ex.InnerException != null)
                ex = ex.InnerException;
            MessageBox.Show("Error: " + ex.Message);
        }
        else if (t.IsCanceled)
        {
            // this should not happen 
            // as you don't pass a CancellationToken into your task
            MessageBox.Show("Canclled.");
        }
        else
        {
            // completed successfully
            MessageBox.Show("Result: " + t.Result);
        }
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

只要您定位.NET 4.0并且您希望.NET 4.0行为用于未观察到的异常(即,当任务被垃圾收集时重新抛出),您应该显式配置它app.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
  <runtime>
    <ThrowUnobservedTaskExceptions enabled="true"/>
  </runtime>
</configuration>

检查以获取更多详细信息:

Unobserved task exceptions in .NET4

答案 1 :(得分:4)

你所拥有的是AggregateException。这是从任务中抛出的,需要您检查内部异常以查找特定的异常。像这样:

task.ContinueWith(t =>
{
    if (t.Exception is AggregateException) // is it an AggregateException?
    {
        var ae = t.Exception as AggregateException;

        foreach (var e in ae.InnerExceptions) // loop them and print their messages
        {
            Console.WriteLine(e.Message); // output is "y" .. because that's what you threw
        }
    }
},
TaskContinuationOptions.OnlyOnFaulted);

答案 2 :(得分:3)

从.Net 4.5开始,您可以使用.col返回“此异常的根本原因”。

https://docs.microsoft.com/en-us/dotnet/api/system.aggregateexception.getbaseexception?view=netframework-4.7.2

虽然文档似乎有点过时了。它声称返回另一个AggregateException。但是我认为你会发现它返回抛出的ArgumentException。

答案 3 :(得分:0)

“发生一个或多个错误”来自任务池生成的包装器异常。如果需要,请使用Console.WriteLine(t.Exception.ToString())打印整个例外。

IDE可以自动捕获所有异常,无论它们是否被处理。

答案 4 :(得分:0)

由于您正在使用任务,因此应该获得AggregateException,其中包含执行期间发生的所有异常。您会看到One or more errors occurred消息,因为它是AggregateException.ToString()方法的默认输出。

您需要异常实例的Handle方法。

另请参阅处理此类异常的正确方法here

答案 5 :(得分:-5)

        try
        {
            var t1 = Task.Delay(1000);

            var t2 = t1.ContinueWith(t =>
            {
                Console.WriteLine("task 2");
                throw new Exception("task 2 error");
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

            var t3 = t2.ContinueWith(_ =>
            {
                Console.WriteLine("task 3");
                return Task.Delay(1000);
            }, TaskContinuationOptions.OnlyOnRanToCompletion).Unwrap();

            // The key is to await for all tasks rather than just
            // the first or last task.
            await Task.WhenAll(t1, t2, t3);
        }
        catch (AggregateException aex)
        {
            aex.Flatten().Handle(ex =>
                {
                    // handle your exceptions here
                    Console.WriteLine(ex.Message);
                    return true;
                });
        }