UIの「応答なし」を撲滅せよ:BackgroundWorkerによる非同期処理の深淵
長年、VB6のレガシーシステムから現代の.NETへと移行する現場を渡り歩いてきた者として、一つ断言できることがある。「UIスレッドを殺す者は、システムを死に至らしめる」。
重厚なDBクエリ、巨大なCSVのパース、あるいはシステム間連携のAPI通信。これらをメインスレッドで実行し、画面をフリーズさせてはならない。顧客やユーザーが「このシステムは止まった」と感じた瞬間、そのエンジニアの信頼は地に落ちる。
今回は、VB.NETにおける非同期処理の古典にして至高のツール、`BackgroundWorker`を軸に、メモリ管理とスレッドセーフの極意を伝授する。
—
1. なぜ「今さら」BackgroundWorkerなのか
非同期処理といえば`async/await`が現代の主流だが、レガシーなWindows Forms環境や、枯れたシステム保守の現場では、依然として`BackgroundWorker`が最も堅牢な選択肢になり得る。
理由はシンプルだ。イベント駆動とコンテキスト同期が標準で実装されているからだ。
`BackgroundWorker`は、バックグラウンドでの処理完了後、自動的にUIスレッドへ戻ってくる。`Invoke`や`BeginInvoke`といった煩雑なデリゲート管理から解放されることは、保守性の観点から見れば非常に大きなアドバンテージとなる。
—
2. 実装:UIフリーズを回避する定石コード
単に動くだけのコードは誰でも書ける。ここでは、メモリ消費を抑え、進捗を正確にフィードバックする構成を示す。
Imports System.ComponentModel
Public Class MainView
‘ 処理の重い作業をバックグラウンドへ追放する
Private WithEvents worker As New BackgroundWorker()
Private Sub Form_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘ 進捗報告を有効化
worker.WorkerReportsProgress = True
‘ キャンセル要求を許可
worker.WorkerSupportsCancellation = True
End Sub
Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
btnStart.Enabled = False
ProgressBar1.Value = 0
‘ 別スレッドでDoWorkを開始
worker.RunWorkerAsync()
End Sub
‘ 【重要】バックグラウンドスレッドで実行される
Private Sub worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles worker.DoWork
For i As Integer = 0 To 100
If worker.CancellationPending Then
e.Cancel = True
Exit For
End If
‘ ここで重い処理を行う(DB通信、ファイルI/Oなど)
System.Threading.Thread.Sleep(50)
‘ 進捗を報告
worker.ReportProgress(i)
Next
End Sub
‘ 【重要】UIスレッドへ戻ってくる
Private Sub worker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles worker.ProgressChanged
ProgressBar1.Value = e.ProgressPercentage
End Sub
Private Sub worker_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles worker.RunWorkerCompleted
btnStart.Enabled = True
If e.Cancelled Then
MessageBox.Show(“処理がキャンセルされました。”)
Else
MessageBox.Show(“処理が完了しました。”)
End If
End Sub
End Class
—
3. シニアエンジニアが意識すべき「メモリとAPIの極意」
オブジェクトの明示的解放(Disposeの徹底)
`BackgroundWorker`自体はManagedなオブジェクトだが、内部でDBコネクションやファイルストリームを開く場合、`Using`構文を必ず使用せよ。GC(ガベージコレクション)に頼り切った設計は、長期間稼働する業務システムにおいて必ずメモリリークを招く。
‘ ダメな例:接続を開きっぱなしにする
‘ 良い例:
Using connection As New SqlConnection(connectionString)
connection.Open()
‘ 処理…
End Using ‘ ここで確実にCloseとDisposeが走る
Windows API呼び出し時の注意点
もしバックグラウンド処理の中で `SendMessage` や `PostMessage` などのWin32 APIを直接叩く必要がある場合、必ずスレッドIDを意識せよ。UIに関連するメッセージは、UIスレッドから発行しないと予期せぬクラッシュを招く。`BackgroundWorker`のイベントハンドラ内はあくまで「バックグラウンド」であることを忘れてはならない。
レガシー環境での性能最適化
古いPC環境で動作させる場合、頻繁な`ReportProgress`はUIスレッドのメッセージキューを飽和させる。進捗更新は「1%ごと」ではなく、タイマーを併用するか、ある程度の粒度(例:5%ごと)で間引くのが、システム全体を安定させる賢者の選択だ。
—
結びに代えて:技術は「道具」に過ぎない
`BackgroundWorker`は、もはや最新の技術ではない。しかし、VB.NETという言語が持つ「現場のニーズに即座に応える」という哲学を最も体現している機能の一つだ。
どんなに高度なAPIを利用しようとも、ユーザーが直感的に「処理中である」と理解でき、かつシステムが安定して稼働すること。それが我々プロフェッショナルに求められる唯一の成果物である。
コードを書くとき、常に問い続けよ。「このスレッドは、ユーザーの邪魔をしていないか?」と。その問いこそが、真の自動化エンジニアへの道標となる。
