泥沼のUIフリーズに終止符を:VB.NETにおける非同期処理の「真の」掌握
かつて、我々は`BackgroundWorker`のイベントハンドラに翻弄されていた。`DoWork`で処理を投げ、`RunWorkerCompleted`で結果を受け取る。あの断片化されたコードは、保守の現場において「スパゲッティの温床」と化していた。
だが、`.NET Framework 4.5`で導入された`Async/Await`は、単なるシンタックスシュガーではない。これは、スレッドの制御権をコンパイラに委ね、非同期処理を「直列的な思考」で記述可能にするためのアーキテクチャ革命だ。
本稿では、レガシーを脱却し、UIを止めずに堅牢な非同期制御を実装する極限の知見を伝授する。
—
1. 非同期の「罠」:なぜUIはフリーズするのか
多くのエンジニアが陥る罠は、`Task.Run`を使えば全てが解決すると錯覚することだ。真の問題は、「いつ、どのスレッドで同期コンテキストに復帰するか」にある。
VB.NETにおいて`Await`を実行すると、現在の`SynchronizationContext`(UIスレッドであればUIのメッセージループ)をキャプチャし、処理完了後にそこへ復帰しようとする。ここで重い処理がUIを占有すれば、当然フリーズする。
究極の非同期実装パターン
単に非同期にするのではなく、タイムアウトとキャンセルを組み込んだ「実戦型」のパターンがこれだ。
”’
”’
Public Async Function FetchDataWithTimeoutAsync(url As String, timeoutMs As Integer) As Task(Of String)
‘ CancellationTokenSourceでタイムアウトを管理
Using cts As New Threading.CancellationTokenSource(timeoutMs)
Try
‘ HttpClientはリソースを食うため、staticで保持するのが鉄則
‘ 戻り値のタスクを直接Awaitする
Dim response As HttpResponseMessage = Await _httpClient.GetAsync(url, cts.Token)
‘ 完了後の後続処理
Return Await response.Content.ReadAsStringAsync()
Catch ex As OperationCanceledException
‘ タイムアウト時の制御。ログに時刻とコンテキストを記録せよ
Throw New TimeoutException(“API通信がタイムアウトしました。”, ex)
Catch ex As Exception
‘ 予期せぬ例外の握り潰しは厳禁
Throw
End Try
End Using
End Function
—
2. 複数のタスクを「効率的に」待ち合わせる
複数のDBクエリを逐次実行してはならない。それはレガシー時代の悪癖だ。`Task.WhenAll`を用い、全タスクを並列駆動させよ。
ただし、注意が必要だ。「無制限の並列実行はメモリを殺す」。接続プールの上限やメモリ負荷を考慮し、セマフォ(`SemaphoreSlim`)を用いたスロットリングが必要となる。
”’
”’
Private Async Function ProcessMultipleQueriesAsync(queries As List(Of String)) As Task
‘ 同時実行数を3に制限
Dim semaphore As New Threading.SemaphoreSlim(3)
Dim tasks = queries.Select(Async Function(q)
Await semaphore.WaitAsync()
Try
‘ ここで重いDB処理を実行
Return Await ExecuteQueryAsync(q)
Finally
semaphore.Release()
End Try
End Function)
‘ 全完了を待機
Dim results = Await Task.WhenAll(tasks)
End Function
—
3. レガシー保守の極意:UIスレッドとの「正しい」対話
古いVB.NETアプリケーションにおいて、`Control.Invoke`を多用して画面を更新するのは、コードの可読性を著しく下げる。`Progress
- Disposeの徹底: `CancellationTokenSource`や`HttpClient`などの`IDisposable`を実装するオブジェクトは、必ず`Using`ブロックに入れるか、クラスのライフサイクルと同期させて解放せよ。メモリリークは、非同期処理の多用により加速する。
- 例外の伝播: `Async Sub`は決して使用してはならない。例外が呼び出し元に伝播せず、プロセスが静かに死ぬ原因となる。必ず`Async Function As Task`に統一し、`Try-Catch`を徹底せよ。
—
チーフアーキテクトからの提言
非同期処理は、単なる「画面を固まらせないための技術」ではない。システムのリソース効率を最大化し、高負荷環境下でも一貫したレスポンスを維持するための防御策である。
レガシーなVB6時代の思考を捨て、`.NETの非同期ステートマシン(コンパイラが裏で生成するコード)がどのようにメモリを管理しているかを想像できるようになった時、君たちは本当の意味でVB.NETを掌握したと言えるだろう。
コードは嘘をつかない。非同期処理の制御権を、直感ではなく「論理」に委ねろ。健闘を祈る。
