web-dev-qa-db-ja.com

Task.Run例外を処理する方法

Task.Runから例外をキャッチするのに問題がありました。コードを変更し、問題を解決しました。次の2つの方法で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);
        }
    });
}
32

タスクが実行されると、タスクの結果またはタスクの完了を待機するときに、スローされた例外は保持され、再スローされます。

Task.Run()は、それを行うために使用できるTaskオブジェクトを返します。

var task = Task.Run(...)

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

新しいバージョンのC#の場合、Task.Wait()の代わりにawaitを使用できます。

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

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

このコードは、単純にタスクレベルから集計例外を再スローします。もちろん、ここで他の形式の例外処理を紹介することもできます。

30
Menno Jongerius

外部コードでは、タスクの開始自体がタスクの本体ではなく例外をスローしないかどうかのみをチェックします。非同期で実行され、それを開始したコードが実行されます。

次を使用できます。

_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
jabko87

待つだけで、例外は現在の同期コンテキストにバブルアップします(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());
2
Diego

私にとっては、エラーが発生した後もTask.Runを続行し、UIがエラーを処理できるようにしました。

私の(奇妙な?)ソリューションは、Form.Timerも実行することです。私のTask.Runにはキューがあり(長時間実行される非UIの場合)、Form.Timerにはキューがあります(UIの場合)。

このメソッドはすでに機能していたため、エラー処理を追加するのは簡単でした。task.Runがエラーを取得すると、エラー情報をForm.Timerキューに追加し、エラーダイアログを表示します。

0
Kurtis Lininger