thread-abort-migration について
thread-abort-migrationは、スレッド終了APIに依存する.NET Frameworkコードを、最新の.NETの協調キャンセル方式へ移行する開発者を支援します。利用パターンを分類して代替策を選び、ビルド、参照、テストで移行を確認します。
主な機能
- 01スレッド終了APIの洗い出し
- 02キャンセルパターン別の利用分類
- 03中断処理を協調キャンセルに置換
- 04ASP.NETのレスポンス終了処理
- 05ビルド、参照、テストの検証
- 06クリーンアップとタイムアウト動作の維持
活用シーン
- 01.NET Frameworkプロジェクトを.NET 6+へ移行
- 02キャンセル可能な処理のThread.Abortを置換
- 03中断ベースのタイムアウト処理を移行
- 04SYSLIB0006などの移行課題を解消
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill thread-abort-migrationインストール後、Claude Code で/thread-abort-migrationを入力して読み込まれたか確認します。
詳細
Thread.Abort の移行
このスキルは、Thread.Abort を使用する .NET Framework のコードを、最新の .NET(6 以降)で必要となる協調的キャンセルモデルへ移行するのに役立ちます。最新の .NET では Thread.Abort を呼び出すと PlatformNotSupportedException がスローされます。マネージドスレッドを強制終了する方法はありません。このスキルでは、まず使用パターンを特定し、適切な置き換え方法を適用します。
使用する場面
Thread.Abortを呼び出す .NET Framework プロジェクトを .NET 6 以降へ移行する場合- 制御フローやクリーンアップ処理に使用されている
ThreadAbortExceptionの catch ブロックを置き換える場合 - 保留中の中断を取り消す
Thread.ResetAbortの呼び出しを削除する場合 - ブロック中のスレッドを起こすために使用している
Thread.Interruptを置き換える場合 - 内部で
Thread.Abortを呼び出すResponse.EndまたはResponse.Redirect(url, true)を使用する ASP.NET コードを移行する場合 - ターゲットフレームワークの変更後に発生した
PlatformNotSupportedExceptionまたはSYSLIB0006の警告を解消する場合
使用しない場面
- コードで
Thread.Join、Thread.Sleep、またはThread.Startのみを使用しており、中断処理もThreadAbortExceptionの catch ブロックもない場合。 これらの API は最新の .NET でも同じように動作するため、移行は不要です。ここで作業を止め、移行が不要であることをユーザーに伝えてください。最新化(例:Task.Run、Parallel.ForEach)を提案する場合は、これらが Thread.Abort の移行とは無関係な任意の改善であり、既存のコードはターゲットフレームワーク上でそのまま正しくコンパイルおよび実行できることを必ず明記してください。 - プロジェクトを今後も .NET Framework 上で使い続ける場合
- Thread.Abort の使用箇所が、自分で制御できないサードパーティライブラリ内にある場合
入力
| 入力 | 必須 | 説明 |
|---|---|---|
| ソースプロジェクトまたはソリューション | はい | Thread.Abort の使用箇所を含む .NET Framework プロジェクト |
| ターゲットフレームワーク | はい | ターゲットとする最新の .NET バージョン(例:net8.0) |
| Thread.Abort の使用箇所 | 推奨 | Thread.Abort、ThreadAbortException、Thread.ResetAbort、または Thread.Interrupt を参照しているファイルやクラス |
ワークフロー
コミット方針: 置き換えパターンごとにコミットし、移行内容をレビューおよび二分探索できるようにします。関連する呼び出し箇所(例:キャンセル可能なすべての作業ループ)は、1 つのコミットにまとめます。
ステップ 1:すべてのスレッド終了処理を洗い出す
コードベースを検索し、スレッド終了関連の API をすべて特定します。
Thread.Abortおよびthread.Abort()(インスタンス呼び出し)- catch ブロック内の
ThreadAbortException Thread.ResetAbortThread.InterruptResponse.End()(ASP.NET Framework で内部的に Thread.Abort を呼び出します)Response.Redirect(url, true)(trueパラメーターによって Thread.Abort が呼び出されます)SYSLIB0006のプラグマによる警告抑制
各使用箇所を記録し、中断の意図を分類します。
ステップ 2:各使用パターンを分類する
各使用箇所を、次のパターンのいずれかに分類します。
| パターン | 説明 | 現代的な代替手段 |
|---|---|---|
| キャンセル可能な作業ループ | 要求に応じて停止する必要があるループを実行するスレッド | ループ内で CancellationToken を確認する |
| タイムアウトの適用 | 制限時間を超過したスレッドを中断する | CancellationTokenSource.CancelAfter または遅延を伴う Task.WhenAny |
| ブロッキング呼び出しの中断 | 起動する必要がある Sleep、WaitOne、または Join でブロックされたスレッド | CancellationToken.WaitHandle を使った WaitHandle.WaitAny、または非同期の代替手段 |
| ASP.NET リクエストの終了 | Response.End または Response.Redirect(url, true) | アクションメソッドから戻る。HttpContext.RequestAborted を使用する |
| 制御フローとしての ThreadAbortException | ThreadAbortException を調べてクリーンアップ処理を決定する catch ブロック | 代わりに OperationCanceledException を捕捉し、明示的にクリーンアップする |
| 実行継続のための Thread.ResetAbort | スレッドを存続させるため、中断を捕捉して ResetAbort を呼び出す | CancellationToken.IsCancellationRequested を確認し、継続するかどうかを判断する |
| 協調しないコードの終了 | キャンセル確認を行うよう変更できないコードを実行するスレッドを終了させる | 作業を別プロセスに移し、Process.Kill を使用する |
重要: 根本的なパラダイムシフトは、プリエンプティブなキャンセル(ランタイムが例外を強制的に挿入する)から協調的なキャンセル(コードが自発的にキャンセル要求を確認して応答する必要がある)への移行です。すべての呼び出し箇所について、対象のコードを協調動作するよう変更できるか評価する必要があります。
ステップ 3:各パターンに代替手段を適用する
- キャンセル可能な作業ループ:
CancellationTokenパラメーターを追加します。ループ条件を置き換えるか、安全なチェックポイントでtoken.ThrowIfCancellationRequested()を追加します。呼び出し元でCancellationTokenSourceを作成し、Thread.Abort()の代わりにCancel()を呼び出します。 - タイムアウトの適用:
new CancellationTokenSource(TimeSpan.FromSeconds(n))またはcts.CancelAfter(timeout)を使用します。トークンを作業に渡します。タスクベースのコードでは、Task.WhenAny(workTask, Task.Delay(timeout, cts.Token))を使い、遅延が先に完了した場合はソースをキャンセルします。キャンセルすると、遅延の内部タイマーも破棄されます。 - ブロッキング呼び出しの中断:
Thread.Sleep(ms)をTask.Delay(ms, token)またはtoken.WaitHandle.WaitOne(ms)に置き換えます。ManualResetEvent.WaitOne()をWaitHandle.WaitAny(new[] { event, token.WaitHandle })に置き換えます。 - ASP.NET リクエストの終了:
Response.End()は完全に削除し、メソッドから戻るだけにします。Response.Redirect(url, true)はResponse.Redirect(url)(trueのendResponseパラメーターなし)に置き換えるか、リダイレクト結果を返します。ASP.NET Core では、長時間実行されるリクエスト処理のキャンセルトークンとしてHttpContext.RequestAbortedを使用します。 - 制御フローとしての ThreadAbortException:
catch (ThreadAbortException)をcatch (OperationCanceledException)に置き換えます。クリーンアップ処理はfinallyブロックまたはCancellationToken.Registerコールバックに移します。OperationCanceledExceptionを捕捉して握りつぶしてはいけません。具体的な回復処理がない限り、例外を伝播させます。 - 実行継続のための Thread.ResetAbort:処理ループでのキャンセル後に次の作業単位へ進めるよう、「中断可能な」作業単位を分割します。スレッドの破棄を防ぐために
ResetAbortに頼らないでください。各作業単位の後にtoken.IsCancellationRequestedを確認し、継続するかどうかを判断します。既存のソースをリセットしようとするのではなく、新しい作業単位ごとに新しいCancellationTokenSource(必要に応じて親トークンにリンク)を作成します。 - 協調しないコードの終了:コードを変更して
CancellationTokenを受け取るようにできない場合(サードパーティーライブラリやネイティブ呼び出しなど)、作業を子プロセスに移します。ホストプロセスは stdin/stdout または IPC を介して通信し、タイムアウトした場合はProcess.Killを呼び出します。
ステップ 4: 削除された API のクリーンアップ
すべてのパターンを移行した後、残っている参照を削除または置き換えます。
| 削除された API | 置き換え |
|---|---|
Thread.Abort() | CancellationTokenSource.Cancel() |
ThreadAbortException の catch ブロック | OperationCanceledException の catch ブロック |
Thread.ResetAbort() | token.IsCancellationRequested を確認し、続行するかどうかを判断します |
Thread.Interrupt() | CancellationToken を介してシグナルを送るか、ManualResetEventSlim を設定します (こちらも廃止予定: .NET 9 の SYSLIB0046) |
Response.End() | 呼び出しを削除し、メソッドから return します |
Response.Redirect(url, true) | endResponse なしの Response.Redirect(url)、またはリダイレクト結果を返します |
#pragma warning disable SYSLIB0006 | Thread.Abort 呼び出しを置き換えた後に削除します |
ステップ 5: 移行の検証
- 新しいフレームワークをターゲットにしてプロジェクトをビルドします。
SYSLIB0006警告が 0 件であり、Thread.Abortに関連するコンパイルエラーがないことを確認します。 - コードベース内に
Thread.Abort、ThreadAbortException、Thread.ResetAbort、またはThread.Interruptへの参照が残っていないか検索します。 - 既存のテストを実行します。テストがクリーンアップやタイムアウトのために
Thread.Abortに依存していた場合は、CancellationTokenを使用するように更新します。 - タイムアウトのシナリオでは、キャンセルが要求された後に処理が妥当な時間内に実際に停止することを確認します。
- ブロッキング呼び出しのシナリオでは、トークンがキャンセルされたときにブロックされたスレッドが速やかに起床することを確認します。
検証
- 移行後のコードに
Thread.Abortへの参照が残っていない -
ThreadAbortExceptionの catch ブロックが残っていない -
Thread.ResetAbortの呼び出しが残っていない -
SYSLIB0006の pragma 抑制が残っていない - プロジェクトがターゲットフレームワークに対してスレッドアボート関連の警告なしでクリーンにビルドされる
- キャンセル可能なすべての処理が
CancellationTokenパラメーターを受け取る - タイムアウトのシナリオで
CancellationTokenSource.CancelAfterまたは同等のものを使用している - ブロッキング呼び出しで
token.WaitHandleを伴うWaitHandle.WaitAnyまたは非同期の代替手段を使用している - 既存のテストがパスするか、協調的キャンセル用に更新されている
よくある落とし穴
| 落とし穴 | 解決策 |
|---|---|
CancellationToken パラメーターを追加したものの、長時間実行されるコードで確認していない | ループ内や負荷の高い処理の間に、定期的なチェックポイントとして token.ThrowIfCancellationRequested() を挿入します。キャンセルが機能するのは、コードがそれに協調する場合のみです。 |
| 呼び出しチェーン全体にトークンを渡していない | チェーン内のすべての非同期メソッドまたは長時間実行されるメソッドで、CancellationToken を受け取り、次のメソッドに渡す必要があります。チェーン内のメソッドがひとつでもトークンを無視すると、その時点でキャンセルが滞ります。 |
CancellationToken によって、Thread.Sleep や socket.Receive などのブロッキングする同期呼び出しが中断されると期待している | これらの呼び出しはトークンを確認しません。Thread.Sleep(ms) を token.WaitHandle.WaitOne(ms) に置き換えます。同期 I/O は、CancellationToken を受け取る非同期オーバーロードに置き換えます。 |
OperationCanceledException をキャッチして握りつぶしている | OperationCanceledException は呼び出し元まで伝播させます。キャンセル後の処理(ログ記録、クリーンアップ、結果の返却)を決定する最上位のオーケストレーション箇所でのみキャッチします。 |
CancellationTokenSource を破棄していない | CancellationTokenSource は IDisposable です。using ステートメントで囲むか、finally ブロックで破棄します。破棄しないと、タイマーやコールバックがリークします。 |
| キャンセルは即座に行われると思い込んでいる | 協調的キャンセルは、次のチェックポイントでのみ有効になります。作業項目が大きい場合や、チェック間隔が長い場合は、キャンセルが遅れることがあります。許容できる遅延に基づいて、チェックポイントの頻度を設計してください。 |
Thread.Abort の代わりに Thread.Interrupt を使用している | Thread.Interrupt も、現代の .NET では推奨されません。WaitSleepJoin 状態のスレッドでのみ機能し、別の例外型である ThreadInterruptedException をスローします。CancellationToken による通知に置き換えてください。 |
クリーンアップ処理を移行せずに ThreadAbortException の catch ブロックを削除している | ThreadAbortException の catch ブロックには、重要なクリーンアップ(ロックの解放、トランザクションのロールバック)が含まれていることがよくあります。catch を削除する前に、この処理を finally ブロックまたは CancellationToken.Register コールバックに移してください。 |
詳細情報
- Thread.Abort breaking change in .NET 6+ —
Thread.AbortがPlatformNotSupportedExceptionをスローする理由 - Cancellation in managed threads —
CancellationTokenを使った協調的キャンセルモデル - CancellationTokenSource class — キャンセルトークンの作成と管理に関する API リファレンス
- SYSLIB0006 warning —
Thread.Abortは非推奨です - SYSLIB0046 warning —
Thread.Interruptは非推奨です(.NET 9 で追加) Task.WaitAsync(CancellationToken)— タスクベースのコードでキャンセル可能な待機を行います(.NET 6 以降)