業務アプリの「応答なし」は恥と思え。Async/Awaitで実現する、止まらないUIの設計術
業務自動化ツールを開発していると、必ずぶち当たる壁がある。巨大なCSVの読み込み、DBからの数万件のデータ取得、あるいはネットワーク越しのAPI通信。これらを実行した瞬間、Windows Formsの画面が白くなり、「応答なし」の文字がタイトルバーに浮かぶ――。
この現象を「処理中だから仕方ない」と放置しているようでは、プロのエンジニアとは呼べない。「UIスレッドをブロックするな、処理を委譲せよ」。 これが、現代のWindowsデスクトップ開発における鉄則だ。
今日は、VB.NETの`Async/Await`パターンを用いて、プログレスバーと連動した「堅牢で、かつ止まらないUI」を実装する極意を伝授する。
—
1. なぜ「UIスレッドのブロック」が許されないのか
Windows Formsアプリケーションは、単一の「UIスレッド」で動いている。このスレッドは、ボタンの描画やクリックイベントの検知など、ユーザーとの対話を一手に引き受けている。
ここで重い処理(例:`File.ReadAllLines`)を同期的に実行するとどうなるか。UIスレッドはファイル操作が終わるまで次のタスク(画面の再描画やイベント処理)に回れない。結果として、Windows OSは「このアプリは死んだ」と判断し、「応答なし」の烙印を押す。
解決策は明確だ。重い処理を「別スレッド(バックグラウンド)」へ逃がし、結果が戻ってきたタイミングでUIスレッドに「復帰」させること。 これを極めて簡潔に記述できるのが、VB.NETの`Async/Await`である。
—
2. 実装の要諦:プログレスバーを伴う非同期処理
単にバックグラウンドへ投げるだけではない。実務では「今どのくらい進んでいるか」をユーザーに示す必要がある。ここで重要なのが`IProgress(Of T)`インターフェースだ。
プロダクションコード例
このコードは、ファイル読み込みの進捗をプログレスバーに反映させるためのテンプレートだ。そのまま業務ツールに組み込んで欲しい。
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks
Public Class MainForm
‘ ボタンクリックで非同期処理を開始
Private Async Sub btnProcess_Click(sender As Object, e As EventArgs) Handles btnProcess.Click
‘ 二重起動防止:ボタンを無効化
btnProcess.Enabled = False
ProgressBar.Value = 0
‘ UIスレッドから進捗通知を受け取るためのProgressオブジェクト
Dim progressIndicator As New Progress(Of Integer)(Sub(percent)
ProgressBar.Value = percent
End Sub)
Try
‘ 非同期処理をAwaitして待機。ここでUIスレッドは解放される
Dim result As String = Await Task.Run(Function() HeavyFileProcessing(“data.csv”, progressIndicator))
MessageBox.Show(“完了しました:” & result)
Catch ex As Exception
MessageBox.Show(“エラー発生: ” & ex.Message)
Finally
btnProcess.Enabled = True
End Try
End Sub
‘ 重いファイル処理(バックグラウンドスレッドで実行される)
Private Function HeavyFileProcessing(filePath As String, progress As IProgress(Of Integer)) As String
Dim lines = File.ReadAllLines(filePath)
Dim total = lines.Length
For i As Integer = 0 To total – 1
‘ 疑似的な重い処理(実際はここで行単位のDB登録や計算を行う)
Thread.Sleep(10)
‘ 進捗報告(UIスレッドへ値を渡す)
If i Mod 10 = 0 Then
Dim percent = CInt((i / total) 100)
progress.Report(percent)
End If
Next
Return “全” & total & “件の処理が完了”
End Function
End Class
—
3. 実務で「バグらせない」ための3つの鉄則
コードをコピペするだけでは不十分だ。現場で生き残るための設計上の注意点を共有しておく。
① `Task.Run` の中ではUIオブジェクトを直接触るな
`Task.Run` で起動したスレッド内で、`TextBox1.Text = “…”` と書いてはならない。これはスレッドセーフではなく、ほぼ確実にクラッシュを引き起こす。UIの更新は、必ず `IProgress` を介するか、`Invoke` を経由すること。
② 例外処理(Try-Catch)は非同期メソッドの内側と外側で考える
非同期メソッド内で発生した例外は、Awaitしている側でキャッチできる。しかし、スレッド内で発生した予期せぬ致命的なエラーは、呼び出し元まで伝播しないこともある。ログ出力は必須だ。
③ キャンセル処理の検討
ファイル処理が数分かかる場合、ユーザーは「キャンセルしたい」と思うはずだ。`CancellationTokenSource` を導入し、処理を途中で安全に中断できる設計にしておくと、ツールの信頼性が格段に向上する。
—
最後に:エンジニアとしての矜持
「動けばいい」というコードは、数ヶ月後に必ず自分自身を苦しめることになる。`Async/Await`を使いこなすことは、単に画面をフリーズさせない技術ではない。「ユーザーの時間を奪わない」というエンジニアとしての誠実さそのものだ。
このパターンをマスターすれば、君が作る業務ツールは、まるでプロの市販ソフトのように軽快に動くはずだ。さあ、今すぐ既存の同期コードをリファクタリングして、快適なUI体験をユーザーに届けよう。
