VB.NETでUIを殺さない:Async/AwaitとIProgress(Of T)で構築する「止まらない」非同期アーキテクチャ
現場のエンジニアからよく聞く悲鳴がある。
「重い処理を走らせたらフォームが白くなり、マウスカーソルが砂時計(読み込み中)のままフリーズして操作不能になる」と。
VB.NETにおけるUIスレッド(メインスレッド)は、描画とユーザー操作という「聖域」だ。ここで数秒以上のブロックを許せば、UXは崩壊する。スレッドを止めず、かつ安全に進捗を報告する。 この実装を泥臭いBackgroundWorkerや`Invoke`の多用で片付けているなら、今すぐ設計を刷新すべきだ。
今回は、現代的な.NETの標準である`Async/Await`と、疎結合な進捗報告を実現する`IProgress(Of T)`を用いた、プロフェッショナルな非同期処理の極意を伝授する。
—
1. 陥りやすい「アンチパターン」を捨てろ
初心者がやりがちな最悪のパターンは、`Control.Invoke`を非同期処理の内部で乱用することだ。
- なぜダメなのか: 非同期処理側が「UIコンポーネントの存在」を知ってしまうと、保守性が極端に下がる。UIの構造が変わっただけでロジック側も改修が必要になるからだ。
- あるべき姿: 処理ロジックは「進捗を投げた(Reportした)」ことだけを知り、受け手(UI)がそれをどう表示するかはUI側に任せるべきだ。
2. 核心:IProgress(Of T) による疎結合な進捗報告
`IProgress(Of T)`は、UIスレッドへのコンテキスト同期を自動的に行ってくれる神クラスだ。これを使えば、`Invoke`の呪縛から解放される。
実装コード:堅牢な非同期処理テンプレート
以下のコードは、UI操作を一切含まず、純粋なロジックとして完結しているため、再利用性が極めて高い。
Imports System.Threading
Imports System.Threading.Tasks
Public Class AsyncProcessor
”’
”’
”’ 進捗率(0-100)を受け取るインターフェース
”’ キャンセル要求を監視するトークン
Public Async Function ExecuteHeavyTask(progress As IProgress(Of Integer), token As CancellationToken) As Task
‘ 非同期処理の開始
For i As Integer = 1 To 100
‘ キャンセル要求があったら即座に終了
token.ThrowIfCancellationRequested()
‘ 重い処理(ファイルI/OやDBアクセスを想定)
Await Task.Delay(100, token)
‘ 進捗を報告(UIスレッドを意識せず報告できる)
progress?.Report(i)
Next
End Function
End Class
—
3. UI側でのスマートな受け取り方
呼び出し元のフォームでは、`Progress(Of T)`のインスタンスを生成し、そのコンストラクタで「値を受け取った時に何をすべきか」を定義する。
Public Class MainView
Private _cts As CancellationTokenSource
Private Async Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
_cts = New CancellationTokenSource()
btnStart.Enabled = False
‘ 進捗報告が届いた時のUI更新ロジックを定義
Dim progressIndicator = New Progress(Of Integer)(Sub(percent)
progressBar.Value = percent
lblStatus.Text = $”処理中: {percent}%”
End Sub)
Try
Dim processor As New AsyncProcessor()
‘ 非同期処理を実行し、完了まで待機
Await processor.ExecuteHeavyTask(progressIndicator, _cts.Token)
MessageBox.Show(“処理が完了しました。”)
Catch ex As OperationCanceledException
lblStatus.Text = “ユーザーによってキャンセルされました。”
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. プロの視点:設計上の注意点
この設計を採用するにあたり、以下の3点は絶対に忘れてはならない。
1. CancellationTokenの徹底:
「ユーザーが閉じるボタンを押したのに処理が止まらない」という不具合は、`CancellationToken`を省略しているのが原因だ。常に渡せ。
2. await Task.Runの使い所:
CPUバウンドな処理(複雑な計算など)であれば`Task.Run`でスレッドプールに投げる必要があるが、I/Oバウンド(DBアクセスやファイル読み込み)なら、I/O操作自体が元々非同期メソッド(`ReadAsync`など)をサポートしているはずだ。無理に`Task.Run`で囲むのはスレッドの無駄遣いである。
3. 例外処理の構造:
`Async Sub`は例外が発生するとプロセスをクラッシュさせる可能性がある。`Async Sub`はイベントハンドラ専用とし、ロジックは必ず`Async Function As Task`に分割せよ。
まとめ:モダンなVB.NETを体現せよ
VB.NETは古い言語ではない。正しく`Async/Await`と`IProgress(Of T)`を組み合わせれば、最新のC#環境と遜色ない堅牢で応答性の高いアプリケーションが構築できる。
重要なのは、「UIに依存しないロジックを書くこと」だ。
このアーキテクチャを習得すれば、あなたの作るツールは「固まる」という汚名を返上し、洗練されたプロフェッショナルなソフトウェアへと昇華するはずだ。
次は、この設計を基盤にして、さらに複雑な並列処理(`Parallel.ForEachAsync`など)へステップアップしていくことを強く推奨する。健闘を祈る。
