VB.NETの極致:Async/AwaitとIProgress(Of T)で構築する「止まらないUI」の設計思想
長年、VBAによる泥臭い業務自動化から、.NETによる堅牢な基幹システム構築まで、私は数多のコードの墓場を見てきた。VB.NETという言語は、しばしば「入門用」という誤解を受けるが、それは誤りだ。この言語は、適切に扱えばWindowsの深淵へと接続できる強力な武器になる。
本稿では、レガシーな同期処理の呪縛から解放され、`Async/Await`と`IProgress(Of T)`を駆使して、UIスレッドを死守しつつ重厚なバックグラウンド処理を制御する「極限の非同期アーキテクチャ」を伝授する。
—
1. なぜ「DoEvents」を捨て去るべきなのか
VBA時代の開発者が最も陥りやすい罠が、`Application.DoEvents`の乱用だ。重いループの中でUIを更新するためにDoEventsを叩くのは、非同期処理の作法としては「最悪手」である。再入可能性(Re-entrancy)によるスタックオーバーフローや、予期せぬイベントの二重起動を招くからだ。
.NETにおける非同期処理の真髄は、「待機」ではなく「解放」にある。スレッドをブロックせず、処理の完了を待機するステートマシンを生成する。これが`Async/Await`の本質だ。
2. IProgress(Of T)がもたらす疎結合なUI連携
バックグラウンド処理とUIスレッドを繋ぐ際、安易にコントロールを引数で渡してはいけない。それはオブジェクトへの依存度を高め、単体テストを不可能にする。
`IProgress(Of T)`インターフェースは、バックグラウンド処理側には「進捗を報告するインターフェース」のみを意識させ、UI側で「どう描画するか」を完結させる。この疎結合こそが、メンテナンス性の高いコードの要諦だ。
3. 実践コード:堅牢な非同期プログレス実装
以下に、実戦でそのまま使えるテンプレートを示す。
Imports System.Threading
Imports System.Threading.Tasks
Public Class ProgressProcessor
”’
”’
Public Async Function ExecuteHeavyTaskAsync(
progress As IProgress(Of Integer),
ct As CancellationToken) As Task
‘ 実際にはここでAPI呼び出しやDB一括更新を行う
For i As Integer = 1 To 100
‘ キャンセル要求を監視(リソースリーク防止の要)
ct.ThrowIfCancellationRequested()
‘ 擬似的な重い処理
Await Task.Delay(50, ct)
‘ 進捗を報告(UIスレッドへのマーシャリングはProgressクラスが自動で行う)
progress?.Report(i)
Next
End Function
End Class
‘ — UIフォーム側 —
Private Async Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
Dim cts As New CancellationTokenSource()
Dim processor As New ProgressProcessor()
‘ プログレス更新用のアクションを定義
Dim progressIndicator As New Progress(Of Integer)(Sub(percent)
ProgressBar1.Value = percent
lblStatus.Text = $”{percent}% 完了”
End Sub)
Try
btnStart.Enabled = False
‘ 非同期実行
Await processor.ExecuteHeavyTaskAsync(progressIndicator, cts.Token)
MessageBox.Show(“処理完了”)
Catch ex As OperationCanceledException
lblStatus.Text = “ユーザーによりキャンセルされました”
Finally
btnStart.Enabled = True
cts.Dispose() ‘ CancellationTokenSourceは必ず解放せよ
End Try
End Sub
4. プロフェッショナルのためのメモリ最適化と注意点
CancellationTokenの重要性
`CancellationTokenSource`を破棄しないのはメモリリークの温床だ。`Try…Finally`ブロックで確実に`Dispose()`することを忘れてはならない。これは、長時間稼働する社内ツールにおいて、メモリ消費量を一定に保つための鉄則だ。
UIスレッドへのマーシャリング
`Progress(Of T)`は、インスタンス化された時点の同期コンテキスト(通常はUIスレッド)を保持する。そのため、バックグラウンドスレッドから`Report`を呼ぶだけで、自動的にUIスレッドへ処理が安全に引き継がれる。`Control.Invoke`を自前で書く必要はもうない。
大規模データ処理における注意
もし扱うデータがメモリを圧迫するなら、`IEnumerable(Of T)`や`IAsyncEnumerable(Of T)`を利用したストリーミング処理を検討せよ。全データをメモリにロードしてから処理するのではなく、必要な分だけを逐次読み込む設計こそが、VB.NETによるエンタープライズ開発の真骨頂である。
—
結びに:伝説を紡ぐアーキテクトへ
技術は常に進化するが、アーキテクチャの根幹にある「スレッドへの敬意」と「リソースの管理」という思想は不変だ。VBA時代に培った業務知識を、VB.NETのモダンな非同期構文で包み込む。それが、我々シニアエンジニアが次世代に残すべき「コードの品格」である。
君のシステムが、止まることなく、かつ安全に稼働し続けることを願っている。もし、さらに深いメモリダンプの解析や、Windows APIを用いた極限の高速化が必要な場面があれば、また門を叩いてくれ。
