C#タスク並列ライブラリに関する本を読んでいて、次の例がありますが、TaskScheduler.UnobservedTaskExceptionハンドラーがトリガーされることはありません。誰かが私に理由について何か手がかりを与えることができますか?
TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs eventArgs) =>
{
eventArgs.SetObserved();
((AggregateException)eventArgs.Exception).Handle(ex =>
{
Console.WriteLine("Exception type: {0}", ex.GetType());
return true;
});
};
Task task1 = new Task(() =>
{
throw new ArgumentNullException();
});
Task task2 = new Task(() => {
throw new ArgumentOutOfRangeException();
});
task1.Start();
task2.Start();
while (!task1.IsCompleted || !task2.IsCompleted)
{
Thread.Sleep( 5000 );
}
Console.WriteLine("done");
Console.ReadLine();
残念ながら、その例ではコードが表示されません。 UnobservedTaskException
は、タスクがGCによって収集され、例外が監視されない場合にのみ発生します。_task1
_および_task2
_への参照を保持している限り、GCは収集せず、例外ハンドラは表示されません。
UnobservedTaskException
の動作を確認するために、次のことを試してみます(考案された例)。
_public static void Main()
{
TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs eventArgs) =>
{
eventArgs.SetObserved();
((AggregateException)eventArgs.Exception).Handle(ex =>
{
Console.WriteLine("Exception type: {0}", ex.GetType());
return true;
});
};
Task.Factory.StartNew(() =>
{
throw new ArgumentNullException();
});
Task.Factory.StartNew(() =>
{
throw new ArgumentOutOfRangeException();
});
Thread.Sleep(100);
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Done");
Console.ReadKey();
}
_
これにより、メッセージが表示されます。最初のThread.Sleep(100)
呼び出しは、タスクがスローするのに十分な時間を提供します。 collect and waitはGCコレクションを強制し、イベントハンドラーを2回起動します。
そのサンプルスニペットでは、例外は「監視されない」ことはありません。ガベージコレクターがタスクインスタンスを削除するまでは。次のように書き直す必要があります。
class Program {
static void Main(string[] args) {
TaskScheduler.UnobservedTaskException += ( object sender, UnobservedTaskExceptionEventArgs eventArgs ) =>
{
eventArgs.SetObserved();
( (AggregateException)eventArgs.Exception ).Handle( ex =>
{
Console.WriteLine("Exception type: {0}", ex.GetType());
return true;
} );
};
Run();
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("done");
Console.ReadLine();
}
static void Run() {
Task task1 = new Task(() => {
throw new ArgumentNullException();
});
Task task2 = new Task(() => {
throw new ArgumentOutOfRangeException();
});
task1.Start();
task2.Start();
while (!task1.IsCompleted || !task2.IsCompleted) {
Thread.Sleep(50);
}
}
}
これを行わないでください。Task.Wait()を使用してください。
このコードを.NET4.5、Visual Studio 2012(デバッグまたはリリース、関係ありません)で実行し、notput ThrowUnobservedTaskException 私のapp.configで:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Test
{
public static class Program
{
private static void Main()
{
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
RunTask();
Thread.Sleep(2000);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.ReadLine();
}
static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
Console.WriteLine("Caught!");
}
private static void RunTask()
{
Task<int> task = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000); // emulate some calculation
Console.WriteLine("Before exception");
throw new Exception();
return 1;
});
}
}
}
そして、例外はUnobservedTaskExceptionハンドラーによってキャッチされます(「Caught!」が出力されます)。