非同期処理のログで「止まった」「キャンセルされた」「タイムアウトした」がごちゃまぜになりやすい。C# では、その土台に CancellationToken があり、表面に出る例外が OperationCanceledException だったり、TimeoutException だったりする。
この記事では、CancellationToken の概念から入り、OperationCanceledException と Timeout の違いをざっくり整理する。実装パターンの詳細は CancellationToken でタイムアウトを実装する も参照。
CancellationToken = 「もうやらなくていい」という合図を下流に渡す仕組みOperationCanceledException(派生の TaskCanceledException 含む)= その合図を受けて処理が止まったときTimeoutException になることもあるHttpClient のタイムアウトは、しばしばキャンセル例外として表面化する非同期メソッドが「途中でやめていい」状態を知るための仕組みが CancellationToken。
using var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
// 別スレッドやタイマーで Cancel
cts.Cancel();
// または時間切れで Cancel
cts.CancelAfter(TimeSpan.FromSeconds(10));
ポイントは、例外を投げること自体が目的ではないこと。まず Token で「やめて」を伝え、処理がそれに気づいて止まった結果として OperationCanceledException が出ることが多い。
CancellationToken ct = default を付けるHttpClient / DB / 自前ループまで同じ Token を下流に渡すHttpContext.RequestAborted が「クライアント切断」の TokenCreateLinkedTokenSource// 親のキャンセル + 10秒タイムアウト、どちらかで止まる
using var linked = CancellationTokenSource.CreateLinkedTokenSource(parentCt);
linked.CancelAfter(TimeSpan.FromSeconds(10));
await DoWorkAsync(linked.Token);
表面の例外だけ見ると紛らわしいので、何が起きたかで分ける。
概念 意味 よく出る例外
────────────────────────────────────────────────────────────────────────
CancellationToken 「やめて」の合図 (まだ例外ではない)
OperationCanceledException 合図を受けて止まった OCE / TaskCanceledException
Timeout(時間切れ) 制限時間を超えた TimeoutException のことも
キャンセル経路(OCE)のことも
CancelAfter で実装すると、時間切れでも例外型は OCE 系になるOperationCanceledException … キャンセル結果の基本型TaskCanceledException … OCE の派生。async / Task 周りでよく見るTimeoutException … 明示的な時間切れ API が出すことがある(ライブラリ依存)HttpClient.Timeout や Token 経由の打ち切りは、しばしば TaskCanceledException として表面化する。ログに「Canceled」とだけ出ても、中身は時間切れのことがある。
HttpClient.Timeout と自前 Token の二重タイムアウトに注意IsCancellationRequested や Token の紐付け元(HttpContext / 親 Token / CancelAfter)を確認try
{
await DoWorkAsync(ct);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
// 呼び出し元が Cancel した想定内
throw;
}
catch (OperationCanceledException)
{
// Token はまだ生きているのに OCE → 別経路の時間切れなどの可能性
// ログに「timeout-like cancel」などと残す
throw;
}
CancellationToken は「やめて」の合図を下流に渡す仕組みOperationCanceledException(派生含む)