如何处理Task.Run异常

时间:2015-08-18 08:12:43

标签: c# multithreading concurrency exception-handling task

我遇到从Task.Run捕获异常的问题我改变了我的代码并解决了我的问题。我愿意弄清楚在这两种方式下处理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);
        }
    });
}

7 个答案:

答案 0 :(得分:28)

当一个任务运行时,它会抛出任何异常,并在等待任务结果或任务完成时重新抛出。

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");
        }
    }
}

答案 1 :(得分:26)

使用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()选项作为参数继续使用,如下所示:

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

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

答案 2 :(得分: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中。

答案 3 :(得分:2)

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

未由用户代码处理的异常。

此错误是良性的。您可以按 F5 继续并查看这些示例中演示的异常处理行为。要防止Visual Studio遇到第一个错误,只需禁用工具>选项>调试>常规”下的“仅我的代码”复选框即可。

答案 4 :(得分:1)

您可以等待,然后异常会上升到当前同步上下文(请参阅Matthew Watson的回答)。或者,如Menno Jongerius所述,您可以SomeModel.find({"a.d": {$gt: value} })保持代码异步。请注意,只有在使用ContinueWith延续选项引发异常时,您才能这样做:

OnlyOnFaulted

答案 5 :(得分:0)

对我来说,我希望我的Task.Run在出错后继续运行,让UI处理错误,因为它有时间。

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

由于此方法对我来说已经起作用,因此添加错误处理是微不足道的:如果task.Run收到错误,它会将错误信息添加到Form.Timer队列,该队列将显示错误对话框。

答案 6 :(得分:-2)

在@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)