【テクニカル・上級編】上級プロフェッショナル向け:VB.NETでのExceptionDispatchInfoを活用したスレッド間例外の正確な再スローとエラー伝播 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

例外の「魂」を殺すな:ExceptionDispatchInfoによるスタックトレース完全保存の極意

VBAからVB.NETへの移行、あるいはレガシーなWindows Formsアプリケーションの保守において、最も多くのエンジニアが犯す致命的なミスがある。それは「例外を握りつぶす」、あるいは「再スローによってスタックトレースを破壊する」という罪だ。

`Catch`ブロックで`Throw ex`と書いていないか? もしそうなら、君はデバッグという名の暗闇の中で、最も重要な「どこで死んだか」という証拠を自ら抹消していることになる。

今日は、非同期処理やスレッド間通信において、例外の正当性を保ちながらメインスレッドへ伝播させる、極限のテクニックを解説する。

1. なぜ「Throw ex」は禁忌なのか

VB.NETの`Catch ex As Exception`内で`Throw ex`を実行すると、CLRは新しい例外としてスタックトレースを再構築する。これにより、本来の発生源(メソッドの深い階層)が隠蔽され、スタックトレースの最上部には「再スローした場所」しか残らない。

特にマルチスレッド環境では、例外は発生元スレッドで捕獲され、それをメインスレッドへ運ぶ必要がある。この際、単に例外オブジェクトを渡すだけでは不十分だ。

2. ExceptionDispatchInfoの真価

`System.Runtime.ExceptionServices.ExceptionDispatchInfo`(以下EDI)は、まさにこのために存在する。これは例外の「状態(スタックトレースを含む)」をキャプチャし、任意のタイミングで、あたかもその場所で発生したかのように例外を再スローできる強力なツールだ。

実践:スレッド間例外の完全移送

以下のコードは、バックグラウンドスレッドで発生した例外を捕獲し、UIスレッド(またはメインスレッド)へ正確に持ち帰るためのテンプレートである。

Imports System.Runtime.ExceptionServices
Imports System.Threading.Tasks

Public Class ExceptionHandler

‘ バックグラウンドの処理を想定
Public Sub ExecuteComplexTask()
Dim capturedException As ExceptionDispatchInfo = Nothing

Try
‘ Windows API呼び出しや重いデータ処理
Throw New InvalidOperationException(“メモリ整合性の問題が発生”)
Catch ex As Exception
‘ 例外の状態をキャプチャ(これがスタックトレースの魂を保存する)
capturedException = ExceptionDispatchInfo.Capture(ex)
End Try

‘ メインスレッドへ戻る(InvokeやDispatcherを利用)
‘ ここでは簡略化のため、別スレッドで再スローするシナリオ
If capturedException IsNot Nothing Then
‘ 任意のタイミングで再スロー。元のスタックトレースは維持される
capturedException.Throw()
End If
End Sub

End Class

3. レガシー環境とメモリ最適化への配慮

シニアエンジニアとして忘れてはならないのは、例外処理がメモリ管理と密接に関係している点だ。

  • IDisposableの徹底: 非同期処理内で生成したアンマネージド・リソース(`SafeHandle`派生オブジェクトやファイルハンドル)は、例外発生時に必ず解放しなければならない。`Try…Finally`ブロックは、例外が捕捉される前であっても必ず実行されることを利用し、明示的に`Dispose()`を呼ぶこと。
  • Finalizerの過信を捨てる: レガシーシステムでメモリ不足(OOM)に悩む場合、GCの自動解放を待つのは愚策だ。VB.NETでは`Using`構文を活用し、スコープを最小化することで、スタック上の参照を速やかに断ち切ることが、GCの負荷を劇的に下げる。

4. チーフアーキテクトからの提言

システム間連携において、遠隔の例外をローカルで再構築するEDIの技術は、もはや「高級テクニック」ではない。「必須の作法」である。

特に、Windows APIをP/Invokeで呼び出す際、`Marshal.GetLastWin32Error()`の結果と例外情報をセットでキャプチャしておく設計にすべきだ。エラーコードとスタックトレース、この二つが揃って初めて、夜中に呼び出される障害対応の地獄から君を救うことができる。

最後に

コードとは、書いた時点では未完成だ。運用という過酷な環境に晒され、例外という名の「悲鳴」を上げたとき、その悲鳴を歪めずに伝えることこそが、プロフェッショナルな設計者の務めである。

この`ExceptionDispatchInfo`を使いこなし、君の書くシステムを「透明性の高い、堅牢な城」へと昇華させてほしい。


追伸:もし君が依然として `On Error Resume Next` の幻影を追っているなら、今すぐそのコードを削除することを推奨する。技術は進化する。それに追いつけない者は、ただの遺物になるだけだ。

タイトルとURLをコピーしました