-
Notifications
You must be signed in to change notification settings - Fork 0
MS_ControlInvoke
nishi_74322014 edited this page Aug 21, 2026
·
1 revision
- 戻る(非同期処理)
「Windows メッセージングキュー(Control.Invoke、.BeginInvoke)」による方式。
補足(なぜこれが必要なのか): 原文が「Windows メッセージング
キューによる方式」と呼んでいる仕組みの前提を補っておく。【UI スレッド アフィニティ】★ Windows のウィンドウは【作成したスレッドに属する】 → そのスレッドのメッセージ ループがメッセージを処理する → 【別スレッドから触ってはならない】 ・[ウィンドウ・システム](MS_WindowSystem) ・[プロセス間通信](MS_InterProcessCommunication) の 「UI は 1 つのスレッドで作る」も同根 【違反するとどうなるか】 ・.NET Framework 1.x … たまたま動くことがある(が壊れる) ・.NET Framework 2.0 以降 … 【InvalidOperationException】★ 「有効ではないスレッド間の操作: コントロール 'xxx' が、 作成されたスレッド以外のスレッドからアクセスされました。」【Control.Invoke が行うこと】 別スレッド UI スレッド │ │ │ Invoke(delegate) │ メッセージ ループが回っている ├──【メッセージをキューに積む】──▶ │ │ ├ 取り出して delegate を実行 │◀────── 完了を待つ ─────────┤ ▼ ▼ → 【UI スレッド上で実行させる】ための仕組み ★ → 内部的には PostMessage / SendMessage 相当
この非同期処理方式では、
- Foreground スレッド(UI スレッド)から Background スレッドを使用して非同期処理を実行し、
- 非同期処理の実行結果を UI スレッドを使用して画面に反映させる事ができる。
補足(
InvokeとBeginInvokeの違い): ページ名に両方が並んでいるが、
性質が大きく違うので明示しておく。
InvokeBeginInvoke呼び出し元 完了を待つ(同期) 待たない(非同期) 戻り値 delegate の戻り値 IAsyncResultデッドロックの危険 ある ★ 少ない 例外 呼び出し元に伝播する EndInvokeで受け取る用途 結果が必要なとき 進捗表示など、投げっぱなしでよいとき // 同期(完了を待つ) label1.Invoke(new Action(() => label1.Text = "完了")); // 非同期(待たない)★ 進捗更新はこちらが適切 label1.BeginInvoke(new Action(() => label1.Text = $"{percent}%"));【Invoke のデッドロック】★ 実務で必ず踏む UI スレッド ── 別スレッドの終了を待っている(Thread.Join / lock) ▲ │ │ │ Invoke で UI スレッドを待つ └──────── 相互に待つ ──────────┘ → 【フリーズする】 【対策】 ・UI スレッドで【ブロックしない】(Join / Wait / .Result を使わない) ・進捗通知は BeginInvoke にする ・そもそも【async/await にする】(後述)★
InvokeRequiredによる定型句も押さえておく。// 従来の定型(自分が UI スレッドかどうかで分岐する) private void SetText(string text) { if (label1.InvokeRequired) label1.Invoke(new Action<string>(SetText), text); else label1.Text = text; }【注意点】 ・【ハンドルが未作成だと InvokeRequired は false を返す】★ → まだ表示されていないコントロールで誤判定する → IsHandleCreated も併せて確認する ・【破棄済みコントロールへの Invoke は例外】 → フォームを閉じた後に非同期処理が完了すると起きる → IsDisposed / CancellationToken で防ぐ
上記をラップするライブラリ
補足(
BackgroundWorkerの仕組みと現況):BackgroundWorkerは
Invokeを隠蔽してくれる点が価値だった。var worker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true }; worker.DoWork += (s, e) => // ← 別スレッドで実行される { for (var i = 0; i < 100; i++) { if (worker.CancellationPending) { e.Cancel = true; return; } worker.ReportProgress(i); } }; worker.ProgressChanged += (s, e) => // ← 【UI スレッドで実行される】★ progressBar1.Value = e.ProgressPercentage; worker.RunWorkerCompleted += (s, e) => // ← 【UI スレッドで実行される】 label1.Text = e.Cancelled ? "中止" : "完了"; worker.RunWorkerAsync();【BackgroundWorker が優れていた点】 ・Invoke を書かなくてよい(イベントが UI スレッドで発火する)★ ・進捗通知・キャンセルの枠組みが最初からある ・例外が RunWorkerCompletedEventArgs.Error に入る (投げっぱなしでプロセスが落ちない) 【現況】 ・【今も動く】が、新規では使わない ・async/await の方が【簡潔で、合成しやすい】★移行メモ(現在の書き方):
// async/await(現在の標準)★ private async void btnStart_Click(object sender, EventArgs e) { btnStart.Enabled = false; var progress = new Progress<int>(p => progressBar1.Value = p); // ← UI スレッドへ try { await Task.Run(() => DoWork(progress, _cts.Token), _cts.Token); label1.Text = "完了"; } catch (OperationCanceledException) { label1.Text = "中止"; } finally { btnStart.Enabled = true; } }【なぜ async/await では Invoke が不要なのか】★ await の後の処理は、 【元の同期コンテキスト(SynchronizationContext)】に戻される → Windows Forms なら WindowsFormsSynchronizationContext → その中身は【Control.BeginInvoke】である → つまり、async/await は 【本ページの仕組みを裏で使っている】 → 手で書かなくてよくなっただけで、原理は変わっていない【IProgress<T> / Progress<T>】 ・生成したスレッドの同期コンテキストを捕捉する ・Report を呼ぶと【UI スレッドで】コールバックが走る → BackgroundWorker.ReportProgress の後継 ★ → 【UI スレッドで生成する】のが前提(別スレッドで new しない)
async voidの注意(C10k problem でも触れた):・イベント ハンドラは async void にせざるを得ない ・それ以外では【async Task にする】 → async void は例外を捕捉できず、プロセスが落ちる ★WPF の場合は
Dispatcherを使う(考え方は同じ)。Dispatcher.Invoke(() => label1.Content = "完了"); // 同期 await Dispatcher.InvokeAsync(() => label1.Content = "完了"); // 非同期
- BackgroundWorkerクラスを使用して進行状況ダイアログを作成する: .NET Tips: C#, VB.NET
https://dobon.net/vb/dotnet/programing/progressdialogbw.html
- Windowsフォームで別スレッドからコントロールを操作するには?:.NET TIPS
https://atmarkit.itmedia.co.jp/ait/articles/0506/17/news111.html - 時間のかかる処理をバックグラウンドで実行するには?[2.0のみ、C#、VB]
https://atmarkit.itmedia.co.jp/fdotnet/dotnettips/436bgworker/bgworker.html
- Control.Invoke メソッド
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.invoke - Windows フォーム コントロールのスレッド セーフな呼び出し
https://learn.microsoft.com/ja-jp/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls - Progress<T> クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.progress-1 - Dispatcher クラス(WPF)
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.threading.dispatcher
Tags: 移行, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。