こんにちは!Windows Formsアプリの開発で「重い処理を実行したら、画面がフリーズしてマウスカーソルがクルクル回り出した……」なんて絶望的な経験はありませんか?
「画面が固まるだけでなく、途中で止められなくて強制終了するしかない」
「進捗バー(ProgressBar)を動かしたいのに、処理が忙しすぎて描画が追いつかない」
マクロの記録や古いVBAの作法から抜け出し、本当の意味でモダンなWindowsアプリケーションを作りたいあなたへ。今回は、`Task.Run`による非同期処理と`CancellationToken`(キャンセルトークン)による安全な処理中断を組み合わせた、プログラミング現場で即戦力となる極上のデザインパターンを伝授します。
ここをクリアすれば、あなたのVB.NETスキルは間違いなくワンランク上のステージに到達しますよ!
—
1. なぜ「画面がフリーズ」するのか?(UIスレッドの掟)
Windows Formsアプリが動いているとき、裏では「UIスレッド」と呼ばれるたった一人の主役が、画面の描画、ボタンクリックの検知、ユーザーからの入力をたった一人で全てこなしています。
ここに何秒もかかる重いループ処理を書いてしまうとどうなるでしょう? 主役がその処理につきっきりになってしまい、「画面を描き直す暇」がなくなるため、ユーザーから見ると「フリーズ(応答なし)」に見えるのです。
解決の鍵:非同期処理(Async / Await)
この問題を解決するのが `Async` と `Await`、そして `Task.Run` です。
「重い処理の裏方作業は別のスタッフ(別スレッド)に丸投げして、UIスレッドは暇にしておこう! 画面のフリーズを防いで、プログレスバーをスルスル動かそう!」というのがこのアプローチの本質です。
—
2. 【実践】安全に動き、キャンセルできるフォームの設計
それでは実際に、ボタンを押すとプログレスバーが進行し、「キャンセル」ボタンを押すと安全に処理を途中で止められるフォームを作ってみましょう。
フォームの配置部品(コントロール)
Visual Studioのデザイナーで、以下のコントロールを配置してください。
1. `Button1` (テキスト:「処理開始」)
2. `Button2` (テキスト:「キャンセル」 / 初期状態は `Enabled = False` に)
3. `ProgressBar1` (初期状態は `Minimum = 0`, `Maximum = 100`)
4. `Label1` (テキスト:「待機中…」)
実装コード(コピペで動く完全版)
以下のコードを、フォームのコードビハインド(`Form1.vb` など)に丸ごと貼り付けてください。
Imports System.Threading
Imports System.Threading.Tasks
Public Class Form1
‘ キャンセル要求を管理するためのトークンを発行・制御するソース
Private cts As CancellationTokenSource
‘ 【処理開始ボタン】
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
‘ 1. コントロールの状態を「実行中」に切り替え
Button1.Enabled = False
Button2.Enabled = True
ProgressBar1.Value = 0
Label1.Text = “処理を実行中…”
‘ 新しいキャンセルトークンソースを作成
cts = New CancellationTokenSource()
Try
‘ 2. 重い処理を別スレッド(Task.Run)に投げ、完了をAwait(待機)する
‘ UIスレッドをブロックしないため、画面はフリーズしません!
AwaitingLongRunningProcess(ProgressBar1, Label1, cts.Token)
Label1.Text = “処理が正常に完了しました!”
MessageBox.Show(“すべての処理が完了しました。”, “通知”, MessageBoxButtons.OK, MessageBoxIcon.Information)
Catch ex As OperationCanceledException
‘ 3. キャンセルされた場合の例外キャッチ
Label1.Text = “処理はユーザーによってキャンセルされました。”
MessageBox.Show(“処理を中断しました。”, “中断”, MessageBoxButtons.OK, MessageBoxIcon.Warning)
Catch ex As Exception
‘ 4. その他の予期せぬエラーキャッチ
Label1.Text = “エラーが発生しました。”
MessageBox.Show($”エラー: {ex.Message}”, “異常終了”, MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
‘ 5. 成功しようがキャンセルされようが、必ずボタンの状態を元に戻す
Button1.Enabled = True
Button2.Enabled = False
ProgressBar1.Value = 0
‘ トークンソースの破棄(メモリリーク防止の作法)
If cts IsNot Nothing Then
cts.Dispose()
cts = Nothing
End If
End Try
End Sub
‘ 【キャンセルボタン】
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
If cts IsNot Nothing Then
‘ キャンセル要求を発行!
cts.Cancel()
Button2.Enabled = False ‘ 連打防止
Label1.Text = “キャンセル処理中…”
End If
End Sub
‘ 【裏方で動く重いバッチ処理】
‘ 別のスレッドで実行されつつ、UIスレッドのProgressBarやLabelを安全に更新します
Private Async Function AwaitingLongRunningProcess(bar As ProgressBar, lbl As Label, token As CancellationToken) As Task
‘ 例として100ステップの処理をシミュレート
For i As Integer = 1 To 100
‘ —【重要】ここでキャンセル要求が来ていないかを常時監視する —
‘ 要求があれば OperationCanceledException を自動スローして安全に脱出します
token.ThrowIfCancellationRequested()
‘ 重い処理の代わり(例:0.05秒待つ)
Await Task.Delay(50, token)
‘ 進捗状況をUIスレッド側に反映
bar.Value = i
lbl.Text = $”処理中… {i}% 完了”
Next
End Function
End Class
—
3. コードの深い解説:ここがプロの技術
初心者が陥りがちな罠と、このコードが優れているポイントを解説します。
① なぜ別スレッドからProgressBarを触っても怒られないのか?
通常、Windows Formsでは「UI部品を作ったスレッド以外から触ってはいけない(クロススレッド例外)」という鉄の掟があります。
しかし、今回のコードでは `Async / Await` を使っているため、`Await Task.Delay(…)` のあとの処理は、自動的に元のUIスレッドに戻って実行されます。そのため、安全に `bar.Value = i` と書くことができるのです。これはVB.NETの非常に強力な恩恵です。
② `CancellationToken` による安全な自爆(中断)
処理を途中で止めるために、かつては `Thread.Abort()` という荒技が使われていましたが、これはメモリを破壊する危険があるため現在はご法度です。
現代の開発では、「お互いに合図を送り合う」仕組みを使います。
- キャンセルボタンが押される $\rightarrow$ `cts.Cancel()` で「やめて!」のフラグを立てる。
- ループ内の `token.ThrowIfCancellationRequested()` がそのフラグを察知し、自ら潔く例外を発生させて安全に処理を終了する。
この「お行儀の良さ」が、堅牢なアプリケーションを作る最大の秘訣です。
③ `Try-Catch-Finally` の鉄壁の布陣
処理が途中でキャンセルされた場合、コードは `Catch ex As OperationCanceledException` に飛びます。そして何より重要なのが `Finally` ブロックです。
「成功したとき」「キャンセルされたとき」「エラーで落ちたとき」、どんな結末を迎えたとしても、ボタンの有効/無効の切り替えやメモリの掃除(`cts.Dispose()`)を確実に実行するため、`Finally` は絶対に欠かせません。
—
まとめ:モダンな非同期開発でワンランク上のプログラマーへ
今回は、`ProgressBar` と `Task.Run`、そして `CancellationToken` を組み合わせたモダンな非同期進捗・キャンセル制御のデザインパターンを解説しました。
- UIスレッドをブロックしない(画面を固まらせない)
- `Await` を使って安全かつ綺麗に非同期処理を記述する
- `CancellationTokenSource` で安全に処理を中断する
この3つの原則さえ押さえておけば、どれほど処理時間が長いバッチ処理やファイル一括処理を実装しても、ユーザーをイライラさせることのない滑らかで洗練されたアプリケーションを構築できます。
ここをクリアできれば、あなたのVB.NETスキルはもう「初心者」ではありません。ぜひ実際の業務アプリやツール開発に応用してみてくださいね!
