「応答なし」は恥と思え。VB.NETで実現する非同期処理の極北
現場のエンジニア諸君、まだ `BackgroundWorker` を使っているのか?
あるいは、進捗を通知するために強引に `Control.Invoke` を連発し、UIスレッドを死なせているのか?
Windows Formsにおいて「応答なし(Not Responding)」という文字が出るのは、UIスレッドが死んでいる証拠だ。現代の業務アプリケーションにおいて、これは単なるバグではない。「エンジニアとしての設計怠慢」だ。
今日は、VB.NETにおける非同期処理の決定版、`IProgress(Of T)` と `ValueTask` を組み合わせた、堅牢かつモダンなUIスレッド分離の作法を伝授する。
—
1. なぜ「古いやり方」が死を招くのか
`BackgroundWorker` は、確かに便利だった。だが、現代の非同期プログラミング(Async/Await)において、それは「レガシーな足枷」だ。
- コンテキストの汚染: イベントベースの非同期処理は、処理の流れを分断し、例外処理を複雑にする。
- リソースの無駄: `Task` はオブジェクトアロケーションを伴うが、高頻度で発生する小さな処理では `ValueTask` を使うべきだ。
- スレッドセーフの甘え: `Control.Invoke` を安易に使うと、デッドロックの温床になる。
我々が目指すべきは、「UIスレッドを一切ブロックせず、かつ進捗を型安全に伝える」設計である。
—
2. 究極の非同期設計:IProgress と ValueTask の実装
以下は、ファイル処理などの重いバックグラウンドタスクを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
_cts = New CancellationTokenSource()
btnStart.Enabled = False
‘ プログレスバーの更新を受け取るハンドラを定義
‘ IProgress(Of T)は自動的にキャプチャされたUIコンテキスト上で実行される
Dim progress = New Progress(Of Integer)(Sub(percent)
ProgressBar1.Value = percent
lblStatus.Text = $”処理中: {percent}%”
End Sub)
Try
‘ 非同期メソッドを呼び出し、完了を待機
Await PerformHeavyTaskAsync(progress, _cts.Token)
MessageBox.Show(“処理が完了しました。”)
Catch ex As OperationCanceledException
lblStatus.Text = “キャンセルされました。”
Finally
btnStart.Enabled = True
End Try
End Sub
”’
”’
Private Async Function PerformHeavyTaskAsync(progress As IProgress(Of Integer), token As CancellationToken) As ValueTask
‘ await Task.Run でスレッドプールに処理を投げる
Await Task.Run(Sub()
For i As Integer = 1 To 100
‘ キャンセル要求の監視
token.ThrowIfCancellationRequested()
‘ 重い処理のシミュレーション
Thread.Sleep(50)
‘ 進捗報告
progress.Report(i)
Next
End Sub, token)
End Function
Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
_cts?.Cancel()
End Sub
End Class
—
3. この設計が「堅牢」である理由
① `IProgress(Of T)` による疎結合
`IProgress` を使うことで、バックグラウンド処理側は「UIがどうやって表示しているか」を一切知る必要がない。ただ値を流すだけだ。これにより、UIの変更がロジックに影響を与えない疎結合な設計が完成する。
② `ValueTask` の戦略的採用
通常、`Task` はヒープ上にオブジェクトを生成する。もしこのメソッドが秒間数千回呼ばれるような設計であれば、GC(ガベージコレクション)が頻発し、逆にパフォーマンスを落とす。`ValueTask` は、処理が即座に完了する場合にメモリ割り当てを回避できるため、高パフォーマンスが求められる箇所での最適解となる。
③ `CancellationToken` による強制終了
「応答なし」を防ぐもう一つの要素は「キャンセル」だ。ユーザーが処理を中断できるようにするのは、UXの鉄則である。`ThrowIfCancellationRequested()` を適切な箇所に配置することで、スレッドを安全に、かつ即座に終了させることができる。
—
4. 現場のリーダーからの警告
最後に一つだけ、忠告しておく。
「データベースやファイルIOの非同期化」は、ただ `Async` をつけるだけでは意味がない。
本質的な速度向上を狙うなら、基盤となるライブラリが `Async` に対応しているかを確認し、`Task.Run` で単にIO待ちをスレッドプールに逃がすだけでなく、非同期API(`ReadAsync` 等)を直接叩くことだ。
VB.NETは古い言語ではない。書く側の書き方が古いだけだ。
このアーキテクチャを理解すれば、君のアプリケーションは「応答なし」という汚名から解放され、プロフェッショナルなツールへと進化する。
さあ、コードを書き換えろ。現場のUIは君の設計を待っている。
