【実務・中級編】BackgroundWorkerからIProgress(Of T)とCancellationTokenSourceへの移行:モダンな非同期キャンセルと進捗通知の完全マスター – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

BackgroundWorkerはもう捨てろ:モダン非同期処理で実現する「落ちない」UI設計の極意

業務自動化ツールを開発していて、画面がフリーズした経験はないだろうか?あるいは、処理を途中で止められず、タスクマネージャーから強制終了したことは?

VB.NETのレガシーな遺物である`BackgroundWorker`を未だに使っているなら、今すぐその手を止めてほしい。あれはUIスレッドとの同期に罠が多く、例外処理が極めて困難な「技術的負債」の温床だ。

今日は、プロのアーキテクトが現場で採用する`IProgress(Of T)`と`CancellationTokenSource`による、堅牢でモダンな非同期キャンセル・進捗通知パターンを伝授する。

—

1. なぜBackgroundWorkerを捨てるべきなのか

`BackgroundWorker`は、スレッド間の同期をイベントベースで行う。この設計は、`RunWorkerCompleted`や`ProgressChanged`といったイベントを散発させ、コードの可読性を著しく低下させる。

特に致命的なのは、「例外の伝搬」と「キャンセル処理の不透明さ」だ。現代の非同期処理では、`async/await`構文と`Task`ベースの非同期パターン(TAP)によって、処理のフローを同期コードのように直感的に記述し、かつ非同期の恩恵をフルに受ける必要がある。

—

2. 実践:モダン非同期パターンのテンプレート

以下のコードは、ファイル処理やDBクエリといった「重い処理」を、UIをフリーズさせずに実行し、進捗を返し、即座にキャンセル可能な構成だ。

実装コード例

Imports System.Threading
Imports System.Threading.Tasks

Public Class MainForm

‘ キャンセルトークンを生成するためのソース
Private _cts As CancellationTokenSource

Private Async Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
‘ 連続クリック防止
btnStart.Enabled = False
btnCancel.Enabled = True
ProgressBar1.Value = 0

‘ CancellationTokenSourceを初期化
_cts = New CancellationTokenSource()

‘ 進捗報告用のプログレスオブジェクトを生成(UIスレッドを自動認識)
Dim progress As New Progress(Of Integer)(Sub(percent)
ProgressBar1.Value = percent
End Sub)

Try
‘ 非同期タスクの実行
Await Task.Run(Sub() DoHeavyWork(progress, _cts.Token), _cts.Token)
MessageBox.Show(“処理が完了しました。”)
Catch ex As OperationCanceledException
MessageBox.Show(“処理はキャンセルされました。”)
Catch ex As Exception
MessageBox.Show($”エラーが発生しました: {ex.Message}”)
Finally
btnStart.Enabled = True
btnCancel.Enabled = False
_cts.Dispose()
End Try
End Sub

Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
‘ キャンセル要請を投げる
_cts?.Cancel()
End Sub

‘ 重い処理(バックグラウンドスレッドで動作)
Private Sub DoHeavyWork(progress As IProgress(Of Integer), token As CancellationToken)
For i As Integer = 1 To 100
‘ キャンセル要求があったら即座に終了
token.ThrowIfCancellationRequested()

‘ ファイル操作やDB接続をシミュレート
Thread.Sleep(50)

‘ 進捗報告
progress.Report(i)
Next
End Sub
End Class

—

3. この設計が「堅牢」である理由

① UIスレッドへの安全なアクセス

`IProgress(Of T)`を使う最大のメリットは、同期コンテキストの自動キャプチャだ。`Progress(Of T)`をUIスレッド上で生成すれば、`Report`メソッドが呼ばれた際、内部で自動的にUIスレッドへマーシャリングしてくれる。`Invoke`や`BeginInvoke`といった忌々しい記述を排除できる。

② 明示的なキャンセル伝搬

`CancellationToken`は、スレッド間をまたぐ「割り込みシグナル」だ。`token.ThrowIfCancellationRequested()`をループの適切な位置に配置することで、リソースを解放しながら安全に中断できる。これはファイル操作において、中途半端なロックや書きかけのファイルが残るリスクを劇的に減らす。

③ 構造化された例外処理

`BackgroundWorker`ではイベント内で例外をキャッチする必要があったが、このパターンなら`try-catch`でメインのロジックを囲める。非同期処理であっても、同期処理と同じ感覚で例外ハンドリングができるため、バグの混入を防げる。

—

4. 現場で生き残るための「鉄則」

  • UIスレッドをブロックするな: `Task.Run`の中で重い処理を行うこと。UIスレッド上で長時間かかる処理(`Thread.Sleep`や同期的なDB接続)を絶対に記述してはならない。
  • キャンセルはこまめにチェック: ループの回数が多い処理や、I/O待ちが続く箇所には必ず`token.ThrowIfCancellationRequested()`を挟む。
  • Disposeの徹底: `CancellationTokenSource`は使い終わったら必ず`Dispose`すること。これを怠ると、メモリリークの温床になる。

—

最後に:エンジニアとしての矜持

レガシーなコードを使い続けることは、その場しのぎの安らぎを得る代わりに、将来の保守担当者に地獄を見せることと同義だ。

`BackgroundWorker`から`async/await`への移行は、単なる書き方の変更ではない。「非同期処理を言語の第一級市民として扱う」というモダンな開発思想への転換だ。

今日からあなたのプロジェクトにこのパターンを導入せよ。それだけで、あなたの書くコードの「信頼性」は一段階上のステージへと引き上げられるはずだ。

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