-
-
Save in-async/25d7f2195c0c07ddf445fcff3269f0e6 to your computer and use it in GitHub Desktop.
CancellationToken ct = new CancellationTokenSource(0).Token; | |
Task task1 = Task.Run(() => throw new OperationCanceledException(ct)); | |
Task task2 = Task.Run(() => ct.ThrowIfCancellationRequested()); | |
Task.WhenAll(task1, task2).ContinueWith(_ => { | |
task1.Status.Dump(); // Canceled | |
task2.Status.Dump(); // Faulted | |
}); |
Ans.
Task.Run(...)
のオーバーロードが異なるから。
実は task1 は Func<Task>
を、 task2 は Action
を引数に取ってる。
task1 の Task.Run(...)
でスローされた OperationCanceledException
は、 一緒に渡された CancellationToken (default) と異なるので、 TaskStatus.Faulted
となる。これは期待通りで、ここまでは task2 も同じ。
task1 は Task.Run(Func<Task>)
なので、 デリゲート実行結果をそのまま返すと Task<Task>
になってしまうが、戻り値の型を Task
にする為に UnwrapPromise
している。
https://github.com/dotnet/runtime/blob/9b7c52fa7c4ea7056ffeb7461554f233c9886dfd/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs#L5500-L5502
UnwrapPromise
は、対象の Task が .Faulted
でも例外が OperationCanceledException
で lookForOce
パラメータが true なら .Canceled
と判断しているので、 task1 は TaskStatus.Canceled
になっている。
https://github.com/dotnet/runtime/blob/9b7c52fa7c4ea7056ffeb7461554f233c9886dfd/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs#L7120-L7125
教訓
Task.Run(...)
を使う際は、どのオーバーロードを使用しているか意識する事。
Task.Run(...)
には似たようなオーバーロードが多く、呼び間違えやすいので、注意する必要がある。
そもそも
Task.Run(() => throw new OperationCanceledException(ct));
が Task.Run(Func<Task>)
にオーバーロード解決されてるのが直感的におかしいんだけどな。仕様らしい。
ref.
気づくのに3日掛かったわ…