奇妙なクロススレッドUIエラー
-
03-07-2019 - |
質問
私は、コンソールまたはGUIの2つのモードを持つWinFormsアプリを書いています。同じソリューション内の3つのプロジェクト。1つはコンソールアプリ用、1つはUIフォーム用、3つ目は2つのインターフェイスが両方とも接続するロジックを保持します。コンソールアプリは完全にスムーズに実行されます。
ユーザーの選択を保持するモデル。IList<T>
があり、TはStep
を実装するローカルオブジェクトINotifyPropertyChanged
であるため、UIではDataGridViewにマウントされます。実行時にはすべて問題なく、オブジェクトの初期状態が画面に反映されます。
各StepResult
オブジェクトは、順番に実行されるタスクです。一部のプロパティは変更され、IListに反映され、DataGridViewに渡されます。
UIバージョンでのこのアクションは、BackgroundWorkerを作成してイベントをUIに戻すことで実行されます。 r
はそれを実行し、結果を示す列挙型である<=>オブジェクト(例:Running、NotRun、OK、NotOK、Caveat)およびメッセージを示す文字列を生成します(ステップが実行されたが、予想される、すなわち警告付き)。通常、アクションにはデータベースとの対話が含まれますが、デバッグモードではランダムに結果が生成されます。
メッセージがnullの場合、問題はありませんが、次のような応答を生成する場合:
StepResult returnvalue = new StepResult(stat, "completed with caveat")
DataGridViewが作成されたスレッド以外のスレッドからアクセスされたというエラーが表示されます。 (必要に応じて呼び出しを処理するカスタムハンドラーにこれを渡します-多分そうではありませんか?)
その後、一意の応答を生成する場合、たとえば乱数を使用する<=>:
StepResult returnvalue = new StepResult(stat, r.ToString());
アクションは問題なく成功し、数値はDataGridViewにきれいに書き込まれます。
私は困惑しています。私はそれが何らかの形で文字列リテラルの問題だと思っていますが、誰もがより明確な説明を思い付くことができますか?
解決
イベントサブスクリプションを介してUIバインディングを行うため、これが役立つかもしれません;これは、通知がUIスレッドに自動的にマーシャリングされるようにBindingList<T>
をサブクラス化する方法を示した先ほど書いた例です。
sync-context(つまり、コンソールモード)がない場合、単純な直接呼び出しに戻るため、オーバーヘッドはありません。 UIスレッドで実行する場合、これは基本的にControl.Invoke
を使用することに注意してください。これは、UIスレッド上にある場合、デリゲートを直接実行するだけです。したがって、データが非UIスレッドから編集されている場合にのみスイッチがあります-必要なものを突き出します;-p
他のヒント
あなたはあなた自身の質問に答えました:-
DataGridViewが作成されたスレッド以外のスレッドからアクセスされたというエラーが表示されます。
WinFormsは、フォームおよびコントロールで実行されるすべてのアクションは、フォームが作成されたスレッドのコンテキストで実行されると主張します。これは複雑ですが、基盤となるWin32 APIに多くの関係があります。詳細については、 The Old New Thing ブログのさまざまなエントリを参照してください。
必要なことは、InvokeRequiredメソッドとInvokeメソッドを使用して、コントロールが常に同じスレッド(擬似コード)からアクセスされるようにすることです。
object Form.SomeFunction (args)
{
if (InvokeRequired)
{
return Invoke (new delegate (Form.Somefunction), args);
}
else
{
return result_of_some_action;
}
}
以前にも同じ問題がありました。たぶんそれについて私が投稿したこの記事は助けになるでしょう。
http://cyberkruz.vox .com / library / post / net-problem-async-and-windows-forms.html
この記事を見つけました-<!> quot; 異なるスレッドからのIBindingListの更新 <!> quot; -非難の指をBindingListに向けた-
BindingListは非同期操作用に設定されていないため、BindingListは、制御対象の同じスレッドから更新する必要があります。
親フォームをISynchronizeInvoke
オブジェクトとして明示的に渡し、BindingList<T>
のラッパーを作成することで問題が解決しました。