質問

コントローラーを使用して実際の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を使用

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top