PowerShell + XAMLの画面を固めない ― UIスレッドと非同期処理を動かして理解する

PowerShellカテゴリを表すパンダのイラスト PowerShell

この記事について
この記事は、生成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やファイルの読取チェックをバックグラウンド化し、結果だけ画面へ返す

事務職向けツールほど「処理中に反応しない=壊れた」と感じやすいため、内部技術の改善が操作性へ直結します。

公式情報・一次情報

GitHubサンプル

文書情報

記事タイトル
PowerShell + XAMLの画面を固めない ― UIスレッドと非同期処理を動かして理解する
作成日
更新日
Source URL
https://papanda925.com/?p=15672

ライセンス: 本記事のうち、当サイトが権利を有する本文・自作図表は、特記なき限り CC BY 4.0 で利用できます。生成AIを活用して作成・編集した内容を含みます。コードについて、別途ライセンス表示またはリンク先GitHubリポジトリのライセンスがある場合は、その条件を優先します。引用・第三者資料・画像・商標等は本ライセンスの対象外です。 利用ポリシー

タイトルとURLをコピーしました