BackgroundWorker OnWorkCompletedはクロススレッド例外をスローします
-
03-07-2019 - |
質問
コントローラーを使用して実際のDAL呼び出しを実行する、データベースページング用の簡単なUserControlがあります。 BackgroundWorker
を使用して重い作業を実行し、 OnWorkCompleted
イベントでいくつかのボタンを再度有効にし、 TextBox.Text
プロパティを変更し、親フォームのイベントを発生させます。
フォームAは、ユーザーコントロールを保持しています。フォームBを開くボタンをクリックすると、「何もしなくても」それを閉じて、データベースから次のページを取り込もうとすると、 OnWorkCompleted
が(メインスレッドではなく)ワーカースレッドで呼び出され、クロススレッド例外をスローします。
現時点では、ハンドラーに InvokeRequired
のチェックを追加しましたが、メインスレッドで OnWorkCompleted
のすべてのポイントが呼び出されるわけではありませんか?なぜ期待どおりに動作しないのですか?
編集:
問題をarcgisと BackgroundWorker
に絞り込むことができました。コマンドをarcmapに追加する次のソリューションがあり、2つのボタンで簡単な Form1
を開きます。
最初のボタンは BackgroundWorker
を実行し、500msスリープし、カウンターを更新します。
RunWorkerCompleted
メソッドでは、 InvokeRequired
をチェックし、メソッドがメインスレッドまたはワーカースレッド内で元々実行されていたかどうかを示すようにタイトルを更新します。
2番目のボタンは Form2
を開きますが、これには何も含まれていません。
最初は、 RunWorkerCompletedare
のすべての呼び出しはメインスレッド内で行われます(予想どおり-少なくとも BackgroundWorker
のMSDN )
Form2
を開いて閉じた後、 RunWorkerCompleted
は常にワーカースレッドで呼び出されます。このソリューションをそのまま問題に残すことができることを追加したい( RunWorkerCompleted
メソッドで InvokeRequired
をチェックする)が、なぜそれが起こっているのかを理解したい私の期待。私の「本物」でメインスレッドで RunWorkerCompleted
メソッドが呼び出されていることを常に知りたいコードです。
BackgroundTesterBtn
の form.Show();
コマンドで問題を特定できた- ShowDialog()
を使用している場合代わりに、問題は発生しません( RunWorkerCompleted
は常にメインスレッドで実行されます)。ユーザーがフォームにバインドされないように、ArcMapプロジェクトで Show()
を使用する必要があります。
また、通常のWinFormsプロジェクトでバグを再現しようとしました。 ArcMapを使用せずに最初のフォームを開くだけの単純なプロジェクトを追加しましたが、その場合、バグを再現できませんでした。 Show()を使用したかどうかにかかわらず、
RunWorkerCompleted
または ShowDialog()
、 Form2
を開く前後。 Form1
の前にメインフォームとして機能する3番目のフォームを追加しようとしましたが、結果は変わりませんでした。
こちらは単純なsln(VS2005sp1)です-必要です
ESRI.ArcGIS.ADF(9.2.4.1420)
ESRI.ArcGIS.ArcMapUI(9.2.3.1380)
ESRI.ArcGIS.SystemUI(9.2.3.1380)
解決
バグのように見えます:
http://connect.microsoft.com/VisualStudio/feedback /ViewFeedback.aspx?FeedbackID=116930
http://thedatafarm.com/devlifeblog/archive/2005 /12/21/39532.aspx
だから防弾(擬似コード)を使用することをお勧めします:
if(control.InvokeRequired)
control.Invoke(Action);
else
Action()
他のヒント
OnWorkCompleted
のすべてのポイントがメインスレッドで呼び出されるのではないですか?なぜ期待どおりに動作しないのですか?
いいえ、違います。
古いスレッドで古いものを実行することはできません。スレッドは、「これを実行してください」と単純に言うことができる丁寧なオブジェクトではありません。
スレッドのより良いメンタルモデルは貨物列車です。それが行くと、それはそれ自身のトラックでオフになります。コースを変更したり、停止したりすることはできません。影響を与えたい場合は、次の駅に到着するまで待機するか(例:手動でイベントを確認するか)、脱線します( Thread.Abort
およびCrossThread例外は電車を脱線させるのとほぼ同じ結果に注意してください!)
Winformsコントロールは、並べ替えこの動作をサポートします(UIスレッドで任意の関数を実行できる Control.BeginInvoke
があります)が、それは、 Windows UIメッセージポンプへの特別なフックと、いくつかの特別なハンドラの作成。上記の類推を行うために、彼らの列車は駅でチェックインし、定期的に新しい方向を探します。そして、あなたはその施設を使用してあなた自身の方向を投稿することができます。
BackgroundWorker
は汎用であるように設計されているため(Windows GUIに関連付けることはできません)、Windowsの Control.BeginInvoke
機能を使用できません。メインスレッドはそれ自体が行う止められない「トレーニング」であると仮定する必要があるため、完了したイベントはワーカースレッドで実行するか、まったく実行しない必要があります。
ただし、winformsを使用している場合、 OnWorkCompleted
ハンドラーでは、 BeginInvoke
を使用して、ウィンドウに another コールバックを実行させることができます上記の機能。このように:
// Assume we're running in a windows forms button click so we have access to the
// form object in the "this" variable.
void OnButton_Click(object sender, EventArgs e )
var b = new BackgroundWorker();
b.DoWork += ... blah blah
// attach an anonymous function to the completed event.
// when this function fires in the worker thread, it will ask the form (this)
// to execute the WorkCompleteCallback on the UI thread.
// when the form has some spare time, it will run your function, and
// you can do all the stuff that you want
b.RunWorkerCompleted += (s, e) { this.BeginInvoke(WorkCompleteCallback); }
b.RunWorkerAsync(); // GO!
}
void WorkCompleteCallback()
{
Button.Enabled = false;
//other stuff that only works in the UI thread
}
RunWorkerCompletedイベントハンドラーは、Resultプロパティにアクセスする前に、常にErrorプロパティとCanceledプロパティをチェックする必要があります。例外が発生した場合、または操作がキャンセルされた場合、Resultプロパティにアクセスすると例外が発生します。
BackgroundWorker
は、デリゲートインスタンスが、インターフェイス ISynchronizeInvoke
をサポートするクラスを指しているかどうかを確認します。 DALレイヤーは、おそらくそのインターフェイスを実装していません。通常、 Form
で BackgroundWorker
を使用しますが、これはそのインターフェイスをサポートします。
DALレイヤーの BackgroundWorker
を使用し、そこからUIを更新する場合、3つのオプションがあります:
- 引き続き
Invoke
メソッドを呼び出します - DALクラスでインターフェイス
ISynchronizeInvoke
を実装し、手動で呼び出しをリダイレクトします(3つのメソッドとプロパティのみです) -
BackgroundWorker
を呼び出す前(UIスレッド上)、SynchronizationContext.Current
を呼び出し、コンテンツインスタンスをインスタンス変数に保存します。SynchronizationContext
はSend
メソッドを提供します。このメソッドはInvoke
が行うことを正確に行います。
GUIでのクロススレッドの問題を回避するための最良のアプローチは、 SynchronizationContextを使用。