如何处理Task.Run异常

Moh*_*ara 35 c# concurrency multithreading 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);
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

Mat*_*son 36

运行任务时,如果某些内容等待任务的结果或任务完成,则会保留并重新抛出它抛出的任何异常.

Task.Run()返回一个Task可用于执行此操作的对象,因此:

var task = Task.Run(...)

try
{
    task.Wait(); // Rethrows any exception(s).
    ...
Run Code Online (Sandbox Code Playgroud)

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

try
{
    await Task.Run(...);
    ...
Run Code Online (Sandbox Code Playgroud)

这更整洁.


为了完整起见,这是一个可编译的控制台应用程序,演示如下使用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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 即使使用较新版本的C#,也不可能总是*使用`await`而不是`Task.Wait()`.来自[await(C#Reference)](https://msdn.microsoft.com/en-us/library/hh156528%28v=vs.140%29.aspx):"**等待**的异步方法必须通过[async](https://msdn.microsoft.com/en-us/library/hh156513.aspx)关键字修改已使用的." (5认同)
  • 如果有的话,`task.Result`也会引发异常. (3认同)
  • @ErmanAkbay 不,在我的示例中,“throwsExceptionAfterOneSecond()”并不是异步的。它应该是一个同步方法,需要一段时间才能运行,然后抛出异常。如果它是异步的,那么我们就不需要使用“Task.Run()”来运行它(您只需等待它),整个示例就会崩溃。请记住,此示例旨在展示如何从“Task.Run()”获取异常。如果你将 `throwsExceptionAfterOneSecond()` 设为异步,我觉得它会分散代码试图演示的内容。 (3认同)

小智 35

使用Task.Wait的想法将完成这一操作,但会导致调用线程(如代码所示)等待并因此阻塞,直到任务完成,这有效地使代码同步而不是异步.

而是使用Task.ContinueWith选项来实现结果:

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

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

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

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

  • 对于火灾和遗忘任务,我通常使用`.ContinueWith(t => ...记录每个((AggregateException)t.Exception).Flatten()。InnerExceptions ...,TaskContinuationOptions.OnlyOnFaulted)`。 (4认同)

mic*_*czy 7

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

您可以使用:

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

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


Die*_*ego 5

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

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