WinForm アプリでバッチ処理を実行しているときに、UI スレッドに譲歩して UI を更新するにはどうすればよいですか?
質問
.NET 3.5 を使用して C# で書かれた WinForms アプリがあります。時間のかかるバッチプロセスが実行されます。アプリでバッチプロセスの実行状況を更新したいと考えています。UI を更新する最善の方法は何ですか?
解決
BackgroundWorker は、あなたが望むオブジェクトのように思えます。
他のヒント
手っ取り早い方法は次のとおりです Application.DoEvents()
ただし、これにより、イベントの処理順序に問題が発生する可能性があります。なのでお勧めできません
問題はおそらく、ui スレッドに譲歩しなければならないということではなく、ui スレッドで処理を実行し、メッセージの処理をブロックしていることです。バックグラウンドワーカー コンポーネントを使用すると、UI スレッドをブロックせずに、別のスレッドでバッチ処理を実行できます。
時間のかかるプロセスをバックグラウンド スレッドで実行します。バックグラウンド ワーカー クラスは、これを行う簡単な方法です。バックグラウンド ワーカー クラスは、進行状況の更新と、イベント ハンドラーが適切なスレッドで呼び出される完了イベントを送信するための簡単なサポートを提供します。これにより、コードがクリーンかつ簡潔に保たれます。
更新を表示するには、進行状況バーまたはステータス バーのテキストを使用する 2 つの最も一般的な方法があります。
覚えておくべき重要な点は、バックグラウンド スレッドで作業を行っている場合、Windows コントロールなどを更新するには UI スレッドに切り替える必要があるということです。
DoEvents について人々が何を言っているかを詳しく説明するために、何が起こるかをここで説明します。
データを含む何らかのフォームがあり、長時間実行されるイベントがそれをデータベースに保存するか、それに基づいてレポートを生成するとします。レポートの保存または生成を開始し、定期的に DoEvents を呼び出して画面を描画し続けるようにします。
残念ながら、画面は単に描画するだけでなく、ユーザーのアクションにも反応します。これは、Winforms アプリによる処理を待機しているすべての Windows メッセージを処理するために現在実行している処理を DoEvents が停止するためです。これらのメッセージには、再描画のリクエストや、ユーザーの入力、クリックなどが含まれます。
したがって、たとえば、データの保存中に、ユーザーは長時間実行されるタスク (たとえば、[ヘルプ] -> [バージョン情報]) にまったく関係のないモーダル ダイアログ ボックスをアプリに表示させるなどの操作を行うことができます。これで、新しいユーザーのアクションに反応できるようになりました 内部 すでに実行されている長時間実行タスク。DoEvents は、呼び出し時に待機していたすべてのイベントが終了すると戻り、長時間実行されるタスクが続行されます。
ユーザーがモーダル ダイアログを閉じなかった場合はどうなりますか?長時間実行されているタスクは、このダイアログが閉じるまで永久に待機します。データベースにコミットしてトランザクションを保持している場合、ユーザーがコーヒーを飲んでいる間、トランザクションを開いたままにすることになります。トランザクションがタイムアウトして永続化作業が失われるか、トランザクションがタイムアウトせずに DB の他のユーザーがデッドロックされる可能性があります。
ここで何が起こっているのかというと、Application.DoEvents によってコードが再入可能になるということです。ウィキペディアの定義を参照してください ここ. 。記事の冒頭で、コードを再入可能にするには次の点に注意してください。
- 静的 (またはグローバル) 非定数データを保持してはなりません。
- 呼び出し元から提供されたデータに対してのみ機能する必要があります。
- シングルトン リソースへのロックに依存してはなりません。
- 再入不可のコンピューター プログラムまたはルーチンを呼び出してはなりません。
WinForms アプリで長時間実行されるコードが呼び出し元によってメソッドに渡されたデータのみで動作し、静的データを保持せず、ロックも保持せず、他の再入可能なメソッドのみを呼び出すことはほとんどありません。
ここで多くの人が言っているように、DoEvents はコード内で非常に奇妙なシナリオを引き起こす可能性があります。それが引き起こす可能性のあるバグは診断が非常に難しい場合があり、ユーザーは「ああ、保存を待っている間にこの無関係なボタンをクリックしたためにこの問題が起こったかもしれない」と言う可能性は高くありません。
バックグラウンドワーカー コンポーネントを使用してバッチ処理を別のスレッドで実行すると、UI スレッドには影響しません。
以前のコメント投稿者が指摘したことを再度述べたいと思います。DoEvents() は、ほとんどの場合「ハッキング」の一種であり、メンテナンスの悪夢を引き起こすため、可能な限り DoEvents() を避けてください。
BackgroundWorker の道を進む場合 (私が推奨する)、コントロールのメソッドやプロパティを呼び出したい場合は、UI へのクロススレッド呼び出しを処理する必要があります。これらはスレッドに依存しており、からのみ呼び出す必要があるためです。作成されたスレッド。必要に応じて、Control.Invoke() および/または Control.BeginInvoke() を使用します。
バックグラウンド/ワーカースレッドで実行している場合は、次のように呼び出すことができます。 Control.Invoke
UI コントロールの 1 つで、UI スレッドでデリゲートを実行します。
Control.Invoke
同期です (デリゲートが戻るまで待機します)。待ちたくない場合は使用してください .BeginInvoke()
コマンドをキューに入れるだけです。
の戻り値 .BeginInvoke()
メソッドが完了したかどうかを確認したり、完了するまで待つことができます。
使用 Backgroundworker
, 、そして、を処理して GUI スレッドも更新しようとしている場合、 ProgressChanged
イベント(たとえば、 ProgressBar
)、必ず設定してください WorkerReportsProgress=true
, そうでない場合、進行状況を報告しているスレッドは、最初に呼び出しを試みたときに終了します。 ReportProgress
...
例外がスローされますが、「スロー時」を有効にしないと例外が表示されない可能性があり、出力にはスレッドが終了したことが示されるだけです。
Application.DoEvents() それとも別のスレッドでバッチを実行しますか?
DoEvents() は私が探していたものでしたが、バックグラウンドワーカーの回答にも賛成票を投じました。これは、もう少し調査する良い解決策のように見えるためです。