如何处理任务.运行异常

本文关键字:任务 运行 异常 处理 何处理 | 更新日期: 2023-09-27 18:08:22

我在捕获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);
        }
    });
}

如何处理任务.运行异常

使用Task的思路。Wait可以达到目的,但会导致调用线程等待(如代码所说),因此阻塞直到任务完成,这有效地使代码同步而不是异步。

请使用Task。选项来实现结果:

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());

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

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

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

在您的Outside代码中,您只检查启动任务是否不会抛出异常,而不是任务本身。它是异步运行的,并且启动它的代码将在此时完成。

你可以使用:

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中。

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

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());

当选项"Just My Code"被启用时,Visual Studio在某些情况下会在抛出异常的那一行中断,并显示一条错误消息:

用户代码未处理的异常。

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

对于我来说,我想要我的任务。在出现错误后继续运行,让UI在有时间时处理错误。

我的(奇怪的?)解决方案是也有一个表单。计时器运行。我的任务。Run有它的队列(用于长时间运行的非ui的东西),和我的表单。计时器有它的队列(用于UI的东西)。

因为这个方法已经为我工作了,所以添加错误处理是微不足道的:Run得到一个错误,它将错误信息添加到Form中。计时器队列,它显示错误对话框。

基于@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)