非同期処理の「魂」を実装する:VB.NETにおけるAsync/AwaitとIProgress(Of T)の極致
長年、VB6の`DoEvents`による「なんちゃって非同期」の悪夢と戦ってきた諸君へ。あるいは、WPFのUIスレッドをフリーズさせ、タスクマネージャーで強制終了を繰り返してきた者たちへ。
VB.NETにおけるモダンな非同期処理の本質は、単なる「画面を止めない技術」ではない。「UIスレッドの神聖さを守り、バックグラウンドの混沌を秩序立てて統合する技術」である。
今回は、単なる入門記事で終わらせない。メモリ管理の深淵からUI応答性の限界までを考慮した、現場で戦うための実装論を叩き込む。
—
1. なぜ「今さら」非同期処理なのか
多くのレガシーシステムは、同期処理の呪縛から逃れられていない。ボタンを押せば処理が終わるまでUIがハングアップし、OSは「応答なし」という冷酷な判決を下す。
これを解決する `Async/Await` は単なる糖衣構文ではない。コンパイラが裏でステートマシンを生成し、コールスタックを再構築する高難易度な魔法だ。この魔法を正しく制御するために、我々は `IProgress(Of T)` というインターフェースを召喚する。
2. 進捗率実装のアーキテクチャ設計
プログレスバーの実装において陥りやすい罠は、「進捗報告の過剰」である。UIスレッドへの通知が秒間数百回発生すれば、メッセージポンプは飽和し、逆にシステム全体のパフォーマンスを殺す。
極限の知見: 進捗更新は「レート制限」を意識せよ。
Imports System.Threading
Imports System.Threading.Tasks
”’
”’
Public Class Processor
‘ IProgress(Of T)はUIスレッドを自動でキャプチャする(SynchronizationContext)
Public Async Function ExecuteHeavyTask(progress As IProgress(Of Integer), ct As CancellationToken) As Task
Dim totalSteps As Integer = 1000
For i As Integer = 1 To totalSteps
‘ キャンセル要求を定常的にチェックする
ct.ThrowIfCancellationRequested()
‘ 実際の重い処理(ダミー)
Await Task.Delay(10)
‘ 1%刻みで報告する(高頻度すぎる更新はUIを破壊する)
If i Mod 10 = 0 Then
progress.Report(CInt((i / totalSteps) 100))
End If
Next
End Function
End Class
3. UIスレッドの設計とメモリ最適化
VB.NETでGUIを扱う際、忘れてはならないのが「オブジェクトのライフサイクル管理」だ。`CancellationTokenSource` は使い終わったら必ず破棄(Dispose)しなければならない。さもなくば、マネージドヒープの断片化を招き、長期間稼働するシステムにおいて致命的なメモリリークの温床となる。
Public Class MainForm
Private _cts As CancellationTokenSource
Private Async Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
‘ 前回の残骸を掃除
_cts?.Dispose()
_cts = New CancellationTokenSource()
Dim progressHandler = New Progress(Of Integer)(Sub(percent)
‘ ここはUIスレッドで安全に実行される
ProgressBar1.Value = percent
End Sub)
Try
btnStart.Enabled = False
Dim proc As New Processor()
Await proc.ExecuteHeavyTask(progressHandler, _cts.Token)
MessageBox.Show(“処理完了”)
Catch ex As OperationCanceledException
‘ キャンセル時のハンドリング
MessageBox.Show(“ユーザーによりキャンセルされました”)
Finally
btnStart.Enabled = True
End Try
End Sub
Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
‘ 処理の停止命令
_cts?.Cancel()
End Sub
End Class
—
4. シニアエンジニアが押さえるべき「極限の知見」
① Windows APIとの協調
もし、この処理の中で非マネージドなDLL(C++等)を呼び出す場合、`Task.Run` を使用して、明示的にスレッドプールへ処理を逃がせ。UIスレッド上で `Declare Function` を呼び出すことは、たとえそれが一瞬であっても「罪」である。
② スレッド境界の意識
`IProgress(Of T)` がなぜUIスレッドに自動で戻ってこれるのか? それは `SynchronizationContext.Current` をコンストラクタでキャプチャしているからだ。もしコンソールアプリやバックグラウンドサービスでこれを使うと、意図した挙動にならない。環境のコンテキストを理解してコーディングせよ。
③ 破棄の重要性
`CancellationTokenSource` は、`IDisposable` を実装している。Using句で囲むことができない非同期のライフサイクルでは、`Finally` ブロックでの明示的な `Dispose()` を徹底すること。レガシー環境の保守において、メモリリークを放置するのは技術者としての敗北だ。
結びに
非同期処理は、システムの「呼吸」を整える技術だ。
VB.NETは古い言語だと言われることもあるが、.NETの強力なランタイムの上で構築される非同期モデルは、現代のどの言語にも引けを取らない洗練さを持っている。
コードを書くとき、画面の向こう側のユーザーが「止まった」と感じない秒数を意識せよ。その微細な配慮の積み重ねこそが、伝説と呼ばれるアーキテクチャの土台となるのだ。
健闘を祈る。
