こんにちは、若きアーキテクトの皆さん。
いつもマクロの自動記録や、単純なボタンクリックの裏側に情熱を注いでいるあなたの姿を、私は頼もしく思っています。
「プログラムを実行したけれど、処理が重すぎてPCが固まってしまった」「途中で止めたくても、強制終了するしかない……」
そんな経験はありませんか?
Excel VBAの「Escキー連打」から卒業し、真のエンジニアとして一歩踏み出すために避けて通れないのが、.NETにおける「非同期処理の制御」です。今日は、VB.NETで重い処理をスマートに、そして安全に中断するための魔法の杖、「CancellationToken」について、その神髄を伝授しましょう。
ここをマスターすれば、あなたのプログラムは「止まらない暴走列車」から、乗客(ユーザー)の要望に完璧に応える「高級リムジン」へと進化しますよ。
—
1. なぜ「キャンセル」が必要なのか?
非同期処理(Async/Await)を使えば、重い計算やネットワーク通信中も画面が固まらなくなります。しかし、画面が動くからこそ、ユーザーはこう思うのです。
「あ、やっぱり今の検索、条件が間違ってたから止めたいな」
「応答が遅すぎるから、10秒経ったら自動で諦めてほしい」
この「やっぱりやめた」や「タイムアウト」を優雅にこなす仕組みが、.NETの標準作法である「キャンセル・トークン・パターン」です。
—
2. 登場人物は2人だけ:Source と Token
この仕組みを理解するために、駅伝をイメージしてください。
1. CancellationTokenSource (CTS)
- 「監督」の役割です。
- 「中止!」という命令を出す権限を持っています。
2. CancellationToken (CT)
- 「タスキ(連絡事項)」の役割です。
- 「中止命令が出ているかどうか」という情報を持ち、選手(非同期タスク)に渡されます。
監督(CTS)が中止の旗を振ると、その情報がタスキ(CT)を通じて選手に伝わり、選手はキリの良いところで走るのをやめる。これが基本構造です。
—
3. 実践:安全に止まる非同期処理の書き方
では、具体的なコードを見てみましょう。
ボタンをクリックすると重い処理が始まり、もう一度別のボタンを押すと安全に止まる仕組みです。
Imports System.Threading
Public Class FrmMain
‘ 監督(Source)を保持する変数。
‘ ライフサイクルを管理するため、クラスレベルで定義します。
Private _cts As CancellationTokenSource
”’
”’
Private Async Sub BtnStart_Click(sender As Object, e As EventArgs) Handles BtnStart.Click
‘ 1. 以前の命令が残っていたら破棄し、新しく監督(Source)を生成
_cts?.Dispose()
_cts = New CancellationTokenSource()
Try
BtnStart.Enabled = False
TxtStatus.Text = “処理を開始しました…”
‘ 2. 監督から「タスキ(Token)」を受け取って、非同期メソッドに渡す
Await DoHeavyWorkAsync(_cts.Token)
TxtStatus.Text = “すべての処理が正常に完了しました!”
Catch ex As OperationCanceledException
‘ 3. キャンセルされた時に投げられる「特別な例外」をキャッチする
TxtStatus.Text = “ユーザーによって処理が中断されました。”
Catch ex As Exception
‘ その他の予期せぬエラー
TxtStatus.Text = $”エラーが発生しました: {ex.Message}”
Finally
BtnStart.Enabled = True
End Try
End Sub
”’
”’
Private Sub BtnCancel_Click(sender As Object, e As EventArgs) Handles BtnCancel.Click
‘ 4. 監督が「中止!」のサインを出す
_cts?.Cancel()
End Sub
”’
”’
”’ タスキ(キャンセル情報)
Private Async Function DoHeavyWorkAsync(ct As CancellationToken) As Task
‘ 10回ほど重いループ処理を行う例
For i As Integer = 1 To 10
‘ 【重要】各ステップの開始時に、中止命令が出ていないか確認する
‘ 中止命令が出ていたら、ここで OperationCanceledException が投げられる
ct.ThrowIfCancellationRequested()
‘ 1秒待機(ここでもタスキを渡すことで、待機中にキャンセルが可能になる)
Await Task.Delay(1000, ct)
‘ 進捗表示など(メインスレッドへの報告)
Console.WriteLine($”{i}番目のステップが完了…”)
Next
End Function
End Class
—
4. ここがプロの視点:なぜ `ThrowIfCancellationRequested` を使うのか?
初心者のうちは、`If ct.IsCancellationRequested Then Return` と書きたくなるかもしれません。しかし、プロのコードでは `ct.ThrowIfCancellationRequested()` を推奨します。
理由は、「キャンセルは異常終了ではなく、合意の上での中断である」ということを、例外(OperationCanceledException)を通じて呼び出し元に明確に伝えるためです。これにより、呼び出し側は「正常に終わったのか」「エラーで落ちたのか」「キャンセルしたのか」を厳密に区別して処理できるようになります。
—
5. 応用:タイムアウトを自動化する
「3秒待っても反応がなければ自動でキャンセルしたい」という場合も、この仕組みなら一行です。
‘ 3000ミリ秒(3秒)後に自動でキャンセルサインを出す設定
_cts = New CancellationTokenSource(3000)
これだけで、ネットワークのタイムアウト処理などが驚くほど簡単に実装できます。監督が最初から「3秒経ったら旗を振るぞ」と決めている状態ですね。
—
6. 陥りやすいエラーと注意点
1. Disposeを忘れない
`CancellationTokenSource` は内部でタイマーなどのリソースを使用します。使い終わったら(あるいは次に新しく作る前に)必ず `Dispose()` を呼び出す癖をつけましょう。
2. ライブラリ関数にも Token を渡す
`Task.Delay` や `HttpClient.GetAsync` など、.NET Framework/.NET Coreの多くの非同期メソッドは、引数に `CancellationToken` を受け取れるようになっています。これらを呼び出す際は、必ず自分の持っているタスキを「横流し」してあげてください。そうすることで、深い階層の処理まで一斉に、安全に止めることができます。
—
結びに代えて
いかがでしたか?
「キャンセル」を制する者は、アプリケーションの「品質」を制します。
ユーザーが「いつでも止められる」という安心感を持って操作できるツールは、それだけで信頼に値する素晴らしいソフトウェアです。
最初は少し難しく感じるかもしれませんが、`Source` を作って `Token` を渡す、このリズムを指に覚え込ませてください。ここをクリアすれば、Visual Basic (VB.NET) の基本、そしてモダンな非同期プログラミングの基礎はバッチリですよ。
あなたのコードが、より洗練され、多くの人を助けるものになることを願っています。
また次の講義でお会いしましょう!
