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

Ani*_*aul 52 .net c# task task-parallel-library

请看下面的代码 -

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;
}
Run Code Online (Sandbox Code Playgroud)

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

throw new ArgumentException("y"); 
Run Code Online (Sandbox Code Playgroud)

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

nos*_*tio 66

你也许并不需要单独OnlyOnFaultedOnlyOnRanToCompletion处理程序,而你不处理OnlyOnCanceled.查看此答案以获取更多详细信息

但是在IDE中进行调试时,控件执行该行时会出现IDE异常消息("用户代码中未处理的异常")

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

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

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

Exception ex = task.Exception;
if (ex != null && ex.InnerException != null)
    ex = ex.InnerException;
Run Code Online (Sandbox Code Playgroud)

要在这种情况下使程序崩溃,您实际上需要在任务操作之外观察异常,例如通过引用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");
}
Run Code Online (Sandbox Code Playgroud)

更多细节:任务和未处理的异常,.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());
}
Run Code Online (Sandbox Code Playgroud)

只要您定位.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>
Run Code Online (Sandbox Code Playgroud)

请查看此内容以获取更多详

.NET4中未观察到的任务异常


Sim*_*ead 5

你所拥有的是一个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);
Run Code Online (Sandbox Code Playgroud)

  • t.Exception是用AggregateException类型声明的,因此足以测试是否为空。不需要as操作。 (4认同)

Cod*_*nes 5

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

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

不过,该文档似乎有点不对劲。它声称返回另一个 AggregateException。不过,我认为您会发现它返回引发的 ArgumentException。


hsu*_*324 0

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

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