この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。Microsoft LearnのWPF threading modelとPowerShell.BeginInvokeの公式資料を確認し、UIスレッドを長時間ブロックせず別pipelineの完了を観察する最小TRYへ整理しています。検証ステータス:📘 Microsoft公式仕様確認済み・Windows実機未確認
PowerShellでWPF/XAMLのツールを作ると、ButtonのClick処理へ Start-Sleep や重い処理をそのまま書いた瞬間、画面が「固まった」ように見えることがあります。WPFの入力・描画と処理を同じUIスレッドで奪い合うためです。
今回のTRY成功条件: 3秒の疑似処理中もWPFウィンドウを動かせて、完了後に [SUCCESS] が表示されること。
Smoke Test:まず「別pipelineを待たずに開始」する
考え方の最小部分は次です。
$worker = [PowerShell]::Create()
[void]$worker.AddScript({
Start-Sleep -Seconds 3
'background work finished'
})
$asyncResult = $worker.BeginInvoke()
"[START] IsCompleted = $($asyncResult.IsCompleted)"
BeginInvoke() は完了まで待つ同期呼び出しではありません。このあとUI側は短い処理だけを続け、完了を観察できます。
WPFではDispatcherがUI処理を順番に扱う
WPFのUIオブジェクトはDispatcherに紐づきます。Clickイベント内で長い同期処理をすると、その間はDispatcherが次の描画・入力を処理しにくくなります。
sequenceDiagram
participant U as User
participant UI as UI thread / Dispatcher
participant W as Worker pipeline
U->>UI: Button Click
UI->>W: BeginInvoke()
W-->>W: 3秒の処理
UI-->>U: 描画・ドラッグを継続
UI->>W: IsCompletedを短く確認
W-->>UI: 完了
UI-->>U: [SUCCESS] を表示
ポイントは「非同期という言葉を使うこと」ではなく、UIスレッドで長時間待たないことです。
完了をDispatcherTimerで観察する
再利用版サンプルでは DispatcherTimer を200ms間隔で動かし、IsCompleted だけを短く確認します。完了後に EndInvoke() で結果を取り出します。
概念部分は次の形です。
$timer.Add_Tick({
if (-not $asyncResult.IsCompleted) {
return
}
$timer.Stop()
try {
$result = $worker.EndInvoke($asyncResult)
$statusText.Text = "[SUCCESS] $($result -join ', ')"
}
catch {
$statusText.Text = "[FAILED] $($_.Exception.Message)"
}
finally {
$worker.Dispose()
}
})
EndInvoke()を処理開始直後に呼ぶと、結局完了を待ってしまいます。完了を確認してから結果を回収する順序が観察ポイントです。
1か所変えてみる
再利用サンプルの次の値を3秒から6秒へ変えます。
Start-Sleep -Seconds 6
待ち時間が倍になってもウィンドウをドラッグできるか確認します。「3秒だから偶然固まらなかった」のではなく、UIとworkerを分けた結果だと理解しやすくなります。
バックグラウンドからUIを直接書き換えない
別pipelineで処理を動かせても、worker側からWPFコントロールを好きに書き換えてよいわけではありません。UIオブジェクトにはthread affinityがあります。
今回のサンプルでは、workerは結果だけ返し、StatusText の更新はUI側のDispatcherTimer内で行います。
終了・後始末までがTRY
ウィンドウを閉じた時に処理だけ残るのは困ります。再利用版ではClose時にtimerを止め、workerが残っていれば停止を試みて Dispose() します。
Window Close
├─ DispatcherTimer.Stop()
└─ workerが存在
├─ Stop()
└─ Dispose()
非同期化すると「開始」だけでなく、キャンセル・例外・終了時の後始末も設計対象になります。
失敗・境界条件
WPFはWindows向け技術で、どのPowerShell環境でも同じ前提ではない
workerへWPFコントロールをそのまま渡して直接更新しない
DispatcherTimerのTick自体へ重い処理を書くと再びUIが止まる
実処理がCPU負荷・I/O・外部APIのどれかで、適切な並行処理方式は変わる
production toolではcancel、timeout、error detailsを追加する
仕事で使うなら
「PowerShellなのに画面付き」の社内補助ツールで効果が出ます。たとえば次の処理です。
大量CSVを読みながら進捗を表示する
複数PCやURLの状態確認中もCancelボタンを押せるようにする
API応答待ちの間に画面を固めない
Excelやファイルの読取チェックをバックグラウンド化し、結果だけ画面へ返す
事務職向けツールほど「処理中に反応しない=壊れた」と感じやすいため、内部技術の改善が操作性へ直結します。
