【问题标题】:How to handle Task.Run Exception如何处理 Task.Run 异常
【发布时间】:2015-11-11 01:44:41
【问题描述】:

我在捕获来自Task.Run 的异常时遇到了问题,该问题已通过如下更改代码得到解决。我想知道这两种方式处理异常的区别:

Outside 方法中我无法捕获异常,但在Inside 方法中我可以。

void Outside()
{
    try
    {
        Task.Run(() =>
        {
            int z = 0;
            int x = 1 / z;
        });
    }
    catch (Exception exception)
    {
        MessageBox.Show("Outside : " + exception.Message);
    }
}

void Inside()
{
    Task.Run(() =>
    {
        try
        {
            int z = 0;
            int x = 1 / z;
        }
        catch (Exception exception)
        {
            MessageBox.Show("Inside : "+exception.Message);
        }
    });
}

【问题讨论】:

  • 如果 OP 没有使用 await,这不是重复的...如果他使用的是 .net 4.x,他就不能使用 await
  • @MatthewWatson 你确定吗? await 是 .NET 4.x!
  • @MatthewWatson 另一方面,他是否使用await 是否重要?答案几乎是一样的。
  • @MatíasFidemraizer 这需要 VS2012 或更高版本。 VS2010 不可能。我应该更准确地说 await 需要 C#5

标签: c# multithreading exception concurrency task


【解决方案1】:

使用 Task.Wait 的想法可以解决问题,但会导致调用线程(如代码所示)等待并因此阻塞,直到任务完成,这实际上使代码同步而不是异步。

改为使用 Task.ContinueWith 选项来获得结果:

Task.Run(() =>
{
   //do some work
}).ContinueWith((t) =>
{
   if (t.IsFaulted) throw t.Exception;
   if (t.IsCompleted) //optionally do some work);
});

如果任务需要在 UI 线程上继续,请使用 TaskScheduler.FromCurrentSynchronizationContext() 选项作为 continue 参数,如下所示:

).ContinueWith((t) =>
{
    if (t.IsFaulted) throw t.Exception;
    if (t.IsCompleted) //optionally do some work);
}, TaskScheduler.FromCurrentSynchronizationContext());

此代码将简单地从任务级别重新抛出聚合异常。当然你也可以在这里介绍一些其他形式的异常处理。

【讨论】:

  • 对于 Fire and Forgot 任务,我通常使用 .ContinueWith(t => ... log every ((AggregateException)t.Exception).Flatten().InnerExceptions ... , TaskContinuationOptions.OnlyOnFaulted)
  • 我没有看到在另一个线程上运行任务以最终等待它完成的意义,您的答案保持并行行为:)
  • 我不明白“抛出 t.Exception”。这是做什么的?我尝试用 try/catch 包围 Task.Run,​​但没有被捕获。
【解决方案2】:

当任务运行时,它抛出的任何异常都会被保留并在等待任务结果或任务完成时重新抛出。

Task.Run() 返回一个 Task 对象,您可以使用它来执行此操作,因此:

var task = Task.Run(...)

try
{
    task.Wait(); // Rethrows any exception(s).
    ...

对于较新版本的 C#,您可以使用 await 而不是 Task.Wait():

try
{
    await Task.Run(...);
    ...

这样更整洁。


为了完整起见,这里有一个可编译的控制台应用程序,演示了await 的使用:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            test().Wait();
        }

        static async Task test()
        {
            try
            {
                await Task.Run(() => throwsExceptionAfterOneSecond());
            }

            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        static void throwsExceptionAfterOneSecond()
        {
            Thread.Sleep(1000); // Sleep is for illustration only. 
            throw new InvalidOperationException("Ooops");
        }
    }
}

【讨论】:

  • 另外task.Result 会抛出异常,如果有的话。
  • 即使使用较新版本的 C#,也并非总是可以使用 await 代替 Task.Wait()。来自await (C# Reference):“使用await的异步方法必须用async关键字修改。”
  • var task = Task.Run(...) 并在 task.Wait() 调用同步之后,当然将异常捕获为所有同步操作。第二个称为从不版本 await Task.Run(...) 调用 async 并且您再次无法捕获异常。或者我搞砸了...... :)
  • @NuriYILMAZ 我猜你搞砸了。 ;) 我附加了一个可编译的示例来说明使用await 来捕获异常。请注意,Main() 中的 test.Wait() 是允许调用异步方法 - 但它没有捕获异常 - await 代码正在捕获异常..
  • @ErmanAkbay 不,在我的示例中,throwsExceptionAfterOneSecond() 不是异步的。它应该是一个同步方法,需要一段时间才能运行,然后抛出异常。如果它异步的,那么我们一开始就不需要使用Task.Run() 来运行它(你只需等待它),整个示例就会崩溃。请记住,此示例旨在说明如何从Task.Run() 获取异常。如果您将 throwsExceptionAfterOneSecond() 设为异步,我觉得它会分散您对代码试图演示的内容的注意力。
【解决方案3】:

在您的外部代码中,您只检查启动任务是否不会引发异常而不是任务的主体本身。它异步运行,然后启动它的代码完成。

你可以使用:

void Outside()
{
    try
    {
        Task.Run(() =>
        {
            int z = 0;
            int x = 1 / z;
        }).GetAwaiter().GetResult();
    }
    catch (Exception exception)
    {
        MessageBox.Show("Outside : " + exception.Message);
    }
}

使用.GetAwaiter().GetResult() 会等到任务结束并按原样传递抛出的异常,并且不会将它们包装在AggregateException 中。

【讨论】:

    【解决方案4】:

    您可以等待,然后异常会冒泡到当前同步上下文(请参阅 Matthew Watson 的回答)。或者,正如 Menno Jongerius 所提到的,您可以 ContinueWith 保持代码异步。请注意,只有在使用 OnlyOnFaulted 延续选项引发异常时才能这样做:

    Task.Run(()=> {
        //.... some work....
    })
    // We could wait now, so we any exceptions are thrown, but that 
    // would make the code synchronous. Instead, we continue only if 
    // the task fails.
    .ContinueWith(t => {
        // This is always true since we ContinueWith OnlyOnFaulted,
        // But we add the condition anyway so resharper doesn't bark.
        if (t.Exception != null)  throw t.Exception;
    }, default
         , TaskContinuationOptions.OnlyOnFaulted
         , TaskScheduler.FromCurrentSynchronizationContext());
    

    【讨论】:

      【解决方案5】:

      启用“仅我的代码”选项后,Visual Studio 在某些情况下会在引发异常的行中断并显示一条错误消息:

      用户代码未处理异常。

      此错误是良性的。您可以按 F5 继续并查看这些示例中演示的异常处理行为。要防止 Visual Studio 在第一个错误时中断,只需禁用 Tools > Options > Debugging > General 下的 Just My Code 复选框。

      【讨论】:

      • 宾果游戏,这是给我的。调试器在 ThrowIfCancellationRequested() 上停止,而不是被调用方法捕获。
      【解决方案6】:

      对我来说,我希望我的 Task.Run 在出现错误后继续运行,让 UI 及时处理错误。

      我的(奇怪的?)解决方案是同时运行 Form.Timer。我的 Task.Run 有它的队列(用于长时间运行的非 UI 的东西),我的 Form.Timer 有它的队列(用于 UI 的东西)。

      因为这个方法已经为我工作了,所以添加错误处理很简单:如果 task.Run 出现错误,它会将错误信息添加到 Form.Timer 队列中,该队列会显示错误对话框。

      【讨论】:

        【解决方案7】:

        基于@MennoJongerius 的回答,以下保持异步并将异常从 .wait 移动到 Async Completed 事件处理程序:

        Public Event AsyncCompleted As AsyncCompletedEventHandler
        
        ).ContinueWith(Sub(t)
                           If t.IsFaulted Then
                               Dim evt As AsyncCompletedEventHandler = Me.AsyncCompletedEvent
                               evt?.Invoke(Me, New AsyncCompletedEventArgs(t.Exception, False, Nothing))
                           End If
                       End Sub)
        

        【讨论】:

          猜你喜欢
          • 2016-09-24
          • 1970-01-01
          • 2016-09-13
          • 2013-01-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多