【问题标题】:Proper way of handling exception in task continuewith在任务继续中处理异常的正确方法
【发布时间】:2014-02-26 13:20:40
【问题描述】:

请看下面的代码-

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”。如果我在调试模式下运行代码,输出和release模式一样,但是在IDE中调试时,控件执行该行时会出现IDE异常信息(“Unhandled exception in user code”)

throw new ArgumentException("y"); 

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

【问题讨论】:

  • 考虑切换到 async/await:这是一种更容易读写的语法,尤其是在处理异常时。
  • @AnirbanPaul,您可能希望根据您的要求更新问题:VS2010 和 .Net 4.0,就像您在 here 所做的那样。

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


【解决方案1】:

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

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

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

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

Task 操作中引发但未处理的异常将不会自动重新引发。相反,它被包装为Task.ExceptionAggregateException 类型)以供将来观察。您可以使用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

【讨论】:

  • 我正在使用 VS2010 和 .Net 4.0。在实际场景中,我会在 Winform 应用程序中应用相同类型的代码。在 OnlyOnFaulted 部分我打算显示错误消息并记录异常,在 OnlyOnRanToCompletion 部分我打算在 CurrentSynchronizationContext 的帮助下更新 UI。我担心的是调试时出现异常消息。这是否意味着未处理异常?请让我知道上述代码模式是否是处理任务异常的安全有效方法?也请建议是否有更好的方法来做到这一点。
  • 如果你知道你有一个AggregateException,使用Flatten()方法不是更容易吗?
  • @takrl,我认为这取决于您是否需要所有聚合异常。见this
  • 您的第一个示例丢失了; task = task.ContinueWith() 因为你扩展了原来的任务
  • @JoelHarkes,第一段代码的目的只是向 OP 展示如何让他的原始代码做他想做的事。它没有展示如何正确地做到这一点,这是在第二个片段中完成的。你的edited version 甚至不会按原样编译,因为task = task.ContinueWith() 将在那里返回Task 而不是Task&lt;int&gt;,所以根本没有Task.Result 可以观察。 请不要编辑它,如果您愿意,请发布您自己的答案
【解决方案2】:

你有一个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);

【讨论】:

  • 在实际场景中,我会在 Winform 应用程序中应用相同类型的代码。在 OnlyOnFaulted 部分我打算显示错误消息并记录异常,在 OnlyOnRanToCompletion 部分我打算在 CurrentSynchronizationContext 的帮助下更新 UI。我担心的是调试时出现异常消息。这是否意味着未处理异常?请让我知道上述代码模式是否是处理任务异常的安全有效的方法?也请建议是否有更好的方法来做到这一点。
  • t.Exception 声明为 AggregateException 类型,因此足以测试是否为空。不需要as 操作。
【解决方案3】:

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

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

不过,文档似乎有点不对劲。它声称返回另一个 AggregateException。但是我想你会发现它返回了被抛出的 ArgumentException。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:
                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;
                        });
                }
        

        【讨论】:

        • -1 因为你的异常永远不会被捕获,因为使用 await 'un-wraps' AggregateException,所以不会有 AggregateExceptions 来捕获 stackoverflow.com/questions/7340309/…>
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-25
        • 1970-01-01
        • 2012-04-08
        相关资源
        最近更新 更多