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);
}
});
}
タスクが実行されると、タスクの結果またはタスクの完了を待機するときに、スローされた例外は保持され、再スローされます。
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");
}
}
}
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());
このコードは、単純にタスクレベルから集計例外を再スローします。もちろん、ここで他の形式の例外処理を紹介することもできます。
外部コードでは、タスクの開始自体がタスクの本体ではなく例外をスローしないかどうかのみをチェックします。非同期で実行され、それを開始したコードが実行されます。
次を使用できます。
_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
継続オプションを使用して例外がスローされた場合にのみ、これを実行できることに注意してください。
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());
私にとっては、エラーが発生した後もTask.Runを続行し、UIがエラーを処理できるようにしました。
私の(奇妙な?)ソリューションは、Form.Timerも実行することです。私のTask.Runにはキューがあり(長時間実行される非UIの場合)、Form.Timerにはキューがあります(UIの場合)。
このメソッドはすでに機能していたため、エラー処理を追加するのは簡単でした。task.Runがエラーを取得すると、エラー情報をForm.Timerキューに追加し、エラーダイアログを表示します。