3

UI スレッド/ディスパッチャに対してメソッドを永続的に非同期で呼び出しているとします。

while (true) {

    uiDispatcher.BeginInvoke(new Action<int, T>(insert_), DispatcherPriority.Normal, new object[] { });
}

プログラムを実行するたびに、アプリケーションの GUI が呼び出しのフラッドにより約 90 秒後にフリーズし始めることがわかります(時間はさまざまですが、およそ 1 ~ 2 分です)。

この過負荷を早期に停止するために、この過負荷が発生した時点を正確に特定 (測定) するにはどうすればよいでしょうか?

付録 I:

私の実際のプログラムでは、無限ループはありません。終了する前に数百回反復するアルゴリズムがあります。すべての反復で、WPF アプリケーションの List コントロールに文字列を追加しています。私は while (true) { ... } 構造を使用しました。実際、アルゴリズムは正しく終了し、すべての (数百の) 文字列がリストに正しく追加されますが、しばらくすると、アルゴリズムが終了するまで GUI を使用できなくなります。その後、GUI は再び応答します。

付録 II:

私のプログラムの目的は、実行中に特定のアルゴリズムを観察することです。追加する文字列はログ エントリです。反復ごとに 1 つのログ文字列です。これらの追加操作を呼び出す理由は、アルゴリズムが UI スレッドとは別のスレッドで実行されているためです。UIスレッド以外のスレッドからUI操作を行うことができないという事実に追いつくために、ある種のThreadSafeObservableCollectionを構築しました(しかし、実際の問題を損なうため、このコードは投稿する価値がないと確信しています。メソッドの繰り返しの高速呼び出しを UI が処理できないことが原因だと思います。

4

7 に答える 7

2

それは非常に簡単です。ユーザーの眼球に過負荷をかけるまでに、間違ったことをしているのです。これは、最新のCPUコアに関する限り、非常に迅速に発生します。1秒あたり20回の更新を超えると、表示される情報がぼやけたように見え始めます。映画館が利用するもの、映画は毎秒24フレームで再生されます。

それよりも速く更新することは、リソースの浪費にすぎません。UIスレッドが座屈し始める前に、まだ膨大な量の呼吸の余地が残っています。それはあなたがそれをするように頼む仕事の量に依存します、しかし典型的にはx50の安全マージンです。Environment.TickCountに基づく単純なタイマーがジョブを完了し、差が45ミリ秒以上になると更新を起動します。

于 2012-07-03T23:44:43.133 に答える
2

それを頻繁に UI に投稿することは危険信号です。別の方法は次のとおりです。新しい文字列を a に入れConcurrentQueue、タイマーで 100 ミリ秒ごとに引き抜くようにします。

非常にシンプルで実装が簡単で、結果は完璧です。

于 2012-07-03T23:53:40.203 に答える
1

私は WPF を使用したことはなく、Windows フォームのみを使用しましたが、非同期で更新する必要がある表示専用コントロールがある場合、それを行う適切な方法は、そのプロパティを変更できるようにコントロールを作成することです。任意のスレッドから自由にアクセスでき、コントロールを更新すると、保留中の更新がまだない場合にのみBeginInvoke更新ルーチンが実行されます。後者の決定は、「フラグ」を使用して行うことができます(プロパティ セッターは、基になるフィールドを変更した後にフラグを呼び出します。フラグがクリアされていた場合は、Int32Interlock.ExchangeInterlocked.ExchangeBeginInvokeリフレッシュ ルーチンについて。その後、リフレッシュ ルーチンがフラグをクリアし、リフレッシュを実行します)。場合によっては、コントロールのリフレッシュ ルーチンが最後に実行されてからの経過時間をチェックし、応答が 20 ミリ秒未満の場合は、タイマーを使用して 20 ミリ秒後にリフレッシュをトリガーすることで、パターンをさらに強化することができます。前回のもの。

.netBeginInvokeは UI スレッドに投稿される多くのアクションを処理できますが、一度に保留中の 1 つのコントロールに対して複数の更新を行うことは無意味です。保留中のアクションをコントロールごとに 1 つ (または多くても少数) に制限すると、キューがオーバーフローする危険がなくなります。

于 2012-07-03T23:23:48.287 に答える
1

スーパーキャットのソリューションをより WPF のような方法で配置するには、MVVM パターンを試してから、スレッド間で共有できる別のビュー モデルクラスを作成し、適切なポイントでロックを解除するか、同時コレクション クラスを使用します。インターフェイスを実装します (INotifyPropertyChanged だと思います)。コレクションが変更されたことを示すイベントを発生させます。このイベントは UI スレッドから発生させる必要がありますが、必要なのは

于 2012-07-03T23:53:43.973 に答える
1

わかりました、コメントの前の悪いリンクで申し訳ありませんが、読み続けました。おそらくこれが役立つでしょう:

The DispatcherOperation object returned by BeginInvoke can be used in several ways to interact with the specified delegate, such as:

Changing the DispatcherPriority of the delegate as it is pending execution in the event queue.
Removing the delegate from the event queue.
Waiting for the delegate to return.
Obtaining the value that the delegate returns after it is executed.

If multiple BeginInvoke calls are made at the same DispatcherPriority, they will be executed in the order the calls were made.

If BeginInvoke is called on a Dispatcher which has shut down, the status property of the returned DispatcherOperation is set to Aborted.

たぶん、あなたが待っているデリゲートの数で何かをすることができます...

于 2012-07-03T23:07:58.737 に答える
0

StopWatchを使用して、最小、最大、平均、最初および最後の更新期間を測定します。(これをUIに出力できます。)

更新頻度は1/(平均更新期間)未満である必要があります。

アルゴリズムの実装を変更して、反復がマルチメディアタイマー(たとえば、この.NETラッパーまたはこの.NETラッパー)によって呼び出されるようにします。タイマーがアクティブになったら、Interlockedを使用して、現在の反復が完了する前に新しい反復が実行されないようにします。メインで反復する必要がある場合は、ディスパッチャーを使用してください。タイマーイベントごとに複数の反復を実行できます。このためのパラメーターを使用し、時間測定とともに、タイマーイベントごとに実行するインタラクションの数とタイマーイベントが必要な頻度を決定します。

タイマーイベントがCPUを窒息させるため、タイマーに5mSec未満を使用することはお勧めしません。

コメントでより簡単に書いたように、メインスレッドにディスパッチするときにDispatcherPriority.Inputを使用します。これにより、UIのCPU時間がディスパッチによって窒息することはありません。これはUIメッセージと同じ優先順位であるため、無視されません。

于 2012-07-04T09:30:18.070 に答える
0

他の人から提供された回答とそれらに対するあなたのコメントを調べた後、あなたの実際の意図は、UI の応答性を維持することであると思われます。これについては、すでに良い提案をいただいていると思います。

それでも、あなたの質問 (UI スレッドの過負荷を検出してフラグを立てる方法) に答えるために、次のことを提案できます。

  1. 最初に「オーバーロード」の定義を決定します (たとえば、「UI スレッドがコントロールのレンダリングを停止し、ユーザー入力の処理を停止する」と想定できます)。
  2. この期間を定義します (たとえば、UI スレッドが最大 40 ミリ秒でレンダリングと入力メッセージを処理し続ける場合、過負荷ではないと言います)。
  3. ここで、オーバーロードの定義に従って DispatcherPriority を設定してDispactherTimerを開始し (私の例では、DispatcherPriority.Input 以下にすることができます)、オーバーロードの「期間」よりも十分に短い間隔を設定します。
  4. タイプ DateTime の共有変数を維持し、タイマーの各ティックでその値を DateTime.Now に変更します。
  5. BeginInvoke に渡すデリゲートでは、現在の時刻と Tick が最後に起動された時刻の差を計算できます。オーバーロードの「測定値」を超えている場合、UI スレッドは定義に従って「オーバーロード」されています。次に、ループ内からチェックできる共有フラグを設定して、適切なアクションを実行できます。

私は認めますが、それは絶対確実ではありませんが、経験的に「測定」を調整することで、影響を受ける前に過負荷を検出できるはずです.

于 2012-07-04T06:34:31.900 に答える