4

I'm working on a heavily data-bound Win.Forms application where I've found some strange behavior. The app has separate I/O threads receiving updates through asynchronous web-requests which it then sends to the main/GUI thread for processing and updating of application-wide data-stores (which in turn may be data-bound to various GUI-elements, etc.). The server at the other end of the web-requests requires periodic requests or the session times out.

I've gone through several attempted solutions of dealing with thread-issues etc. and I've observed the following behavior:

  1. If I use Control.Invoke for sending updates from I/O-thread(s) to main-thread and this update causes a MessageBox to be shown the main form's message pump stops until the user clicks the ok-button. This also blocks the I/O-thread from continuing eventually leading to timeouts on the server.

  2. If I use Control.BeginInvoke for sending updates from I/O-thread(s) to main-thread the main form's message pump does not stop, but if the processing of an update leads to a messagebox being shown, the processing of the rest of that update is halted until the user clicks ok. Since the I/O-threads keep running and the message pump keeps processing messages several BeginInvoke's for updates may be called before the one with the message box is finished. This leads to out-of-sequence updates which is unacceptable.

  3. I/O-threads add updates to a blocking queue (very similar to Creating a blocking Queue<T> in .NET?). GUI-thread uses a Forms.Timer that periodically applies all updates in the blocking queue. This solution solves both the problem of blocking I/O threads and sequentiality of updates i.e. next update will be never be started until previous is finished. However, there is a small performance cost as well as introducing a latency in showing updates that is unacceptable in the long run. I would like update-processing in the main-thread to be event-driven rather than polling.

So to my question. How should I do this to:

  1. avoid blocking the I/O-threads
  2. guarantee that updates are finished in-sequence
  3. keep the main message pump running while showing a message box as a result of an update.

Update: See solution below

4

4 に答える 4

5

MessageBox 自体がメッセージ ループをポンピングします。もちろん、これは Windows フォームのメッセージ ループではありません。すべてが通常どおり実行されますが、Control.BeginInvoke() によってポストされたデリゲート呼び出し要求のディスパッチは除外されます。これを行うことができるのは、Windows フォームのメッセージ ループだけです。

これは、UI スレッドで MessageBox.Show() 呼び出しが行われたときに発生します。ただし、ワーカー スレッドで作成された場合ではなく、メッセージ キューはスレッドごとのプロパティです。Show 呼び出しをワーカーに委任できる場合は、おそらく問題を解決できます。

質問への対処:

  1. 本当に反対のことが必要です: ワーカー スレッドはブロックする必要があります。ブロックしないと重大な問題が発生する可能性があり、BeginInvoke ディスパッチ キューが際限なくいっぱいになります。考えられるトリックの 1 つは、BeginInvoke 呼び出しの数をカウントし、デリゲート ターゲットでカウント ダウンすることです。Interlocked クラスを使用します。

  2. BeginInvoke ターゲットの実行順序は保証されています。本当の問題は、おそらくワーカー スレッドが同期していないことに関連しています。

  3. スレッドにメッセージ ボックスを表示します。

于 2010-04-08T11:45:59.253 に答える
1

したがって、実行し続けたい複雑なデータ取得および処理チェーンがありますが、そこに MessageBox を挿入します。Threading+Invoke では、MessageBox が Modal であり、それが閉じるのを待つ必要があるという事実を変更することはできず、チェーン全体がユーザーが何かをクリックすることに依存します。

そのため、少なくともメイン パスで MessageBox を削除します。処理のセグメントでユーザーの介入が必要な場合、そのセグメントは別のスレッドにある必要があります。

于 2010-04-08T09:08:55.130 に答える
1

Forms.Timer を使用してキューから更新を適用するのではなく、別のスレッドを使用してください。このスレッドはキューを継続的に監視し、(おそらく) 新しいデータでいつ更新するかを GUI に通知します (BeginInvoke を介して) MessageBox は、このキュー リーダー スレッドから表示できます - GUI スレッドである必要はありません。


編集: キュー コンシューマーは Control.Invoke を呼び出して messageBox を表示し、z オーダーの問題を回避できます。

于 2010-04-08T13:01:27.013 に答える
0

これが私が最終的に得た解決策です:

  • I / Oスレッドは、すべての更新をスレッドセーフ/ロックキューに入れます。
  • 個別のワーカースレッドが無限にスピンし、更新をDequeingしてから、GUIスレッドにBeginInvokeします。
  • 更新に応じたGUIスレッドでのMessageBoxの表示は、BeginInvokeで実行されるようになりました。

このソリューションには、以前のソリューション(GUI更新のポーリングを使用した上記の3.で説明)と比較して、次の利点があります。

  1. ポーリングではなく、イベント駆動型のGUIの更新。これにより、(理論的には)パフォーマンスが向上し、遅延が少なくなります。
  2. GUIの更新もI/Oもメッセージボックスによってロックされません。

更新:このソリューションを使用してメッセージボックスが表示されている間、GUI更新はまだロックされているようです。これが修正されると更新されます。

更新2:InvokeをBeginInvokeに変更することにより、ワーカースレッドの修正で更新されました。

于 2010-04-12T11:03:11.623 に答える