8

繰り返しますが、皆さんにとって簡単な質問かもしれません。Silverlight の舞台裏で行われていることについて、私はあまり理解していません。

ポイントを追加および削除して、20 ミリ秒ごとに更新されるローリング スコープとして使用するグラフ作成アプリ (Visiblox) があります。擬似コード:

List<Point> datapoints= new List<Point>();
Series series = new Series(datapoints);
void timer_tick(){
  datapoints.Add(new Point);
  datapoints.RemoveAt(0);
  // no need to refresh chart, it does refresh automatically
}

このチャート作成ツールで 6 シリーズを実行すると、少し遅くなり始めました。ティックを 10ms に変更しても何の違いもありませんでした。チャートは同じ速度で更新されたので、20ms が速度制限 (UI またはチャート?) のようです。

で試してみたCompositionTarget.Renderingところ、同じ結果が得られました。20ms 以下では、速度に違いはありませんでした。

その後、誤って両方を有効にして速度が 2 倍になりました。そこで、複数のスレッド (2、3、4) でテストし、速度を 2 倍、3 倍、4 倍にしました。ロックを生成する必要があるプロセスがわからないため、これにはまだロックがありませんが、データの破損やメモリリークはありません。

私が持っている質問は、なぜ 20 ミリ秒で遅いチャートが 10 ミリ秒で実行できないのに、マルチスレッド化すると途方もなく高速になるのかということです。UI の更新プロセスは高速化されていますか? チャートの計算は 2 倍になりますか? または、単一の DispatcherTimer を実行できる速度に制限はありますか?

ありがとう!


編集:私は組み込みコーディングのバックグラウンドを持っているので、スレッドとタイミングについて考えると、すぐにハードウェアのピンを切り替えてスコープを接続してプロセスの長さを測定することを考えます。私は C# のスレッドが初めてで、スコープを接続するためのピンがありません。スレッドのタイミングをグラフィカルに表示する方法はありますか?

4

2 に答える 2

7

UIスレッドでTickイベントを発生させるDispatcherTimerは、そのIntervalが事実上「最後のティックからxより早くティックする」ことを意味するため、低解像度または低精度のタイマーと見なされます。UIスレッドが何か(入力の処理、グラフの更新など)を実行するのに忙しい場合は、タイマーのイベントが遅延します。さらに、非常に低い間隔でUIスレッドを大量のDispatcherTimerがチェックすることで、アプリケーションの応答性も低下します。これは、Tickイベントが発生している間、アプリケーションが入力に応答できないためです。

したがって、ご指摘のとおり、データを頻繁に処理するには、バックグラウンドスレッドに移動する必要があります。ただし、注意点があります。現在、破損やその他のバグを観察していないという事実は、まったくの偶然である可能性があります。フォアグラウンドスレッドがリストから読み取ろうとしているのと同時にバックグラウンドスレッドでリストが変更されている場合、最終的にクラッシュするか(運が良ければ)、破損したデータが表示されます。

あなたの例では、「チャートを更新する必要はありません。自動的に更新されます」というコメントがあります。datapointsこれは、あなたがコレクションを変更したことをチャートがどのように知っているのか疑問に思いますか?List<T>変更されてもイベントは発生しません。Iを使用している場合ObservableCollection<T>は、ポイントを削除/追加するたびにグラフが更新される可能性があり、速度が低下する可能性があることを指摘します。

しかし、実際に使用している場合はList<T>、チャートを更新している他の何か(おそらく別のタイマー?)が必要です。チャートコントロール自体に自動更新メカニズムが組み込まれているのではないでしょうか。

いずれにせよ、問題は少しトリッキーですが、完全に新しいわけではありません。バックグラウンドスレッドでコレクションを維持し、UIスレッドからコレクションにバインドする方法がいくつかあります。ただし、UIの更新が速いほど、バックグラウンドスレッドがロックを解放するのを待つ可能性が高くなります。

LinkedList<T>これを最小限に抑える1つの方法は、の代わりにを使用することですList<T>。LinkedListの最後に追加するのはO(1)なので、アイテムを削除します。List<T>アイテムを最初から削除するときは、すべてを1つ下にシフトする必要があります。LinkedListを使用すると、バックグラウンドスレッドでロックすることができ、ロックを保持している時間を最小限に抑えることができます。UIスレッドでは、同じロックを取得し、リストを配列にコピーするか、ロックが保持されている間にグラフを更新する必要があります。

別の可能な解決策は、バックグラウンドスレッド上のポイントの「チャンク」をバッファリングし、それらのバッチをDispatcher.BeginInvokeを使用してUIスレッドに投稿することです。ここで、コレクションを安全に更新できます。

于 2011-01-12T01:59:20.460 に答える
4

ここで重要なのは、Silverlightがデフォルトで最大フレームレート60fpsでレンダリングされることを理解することです(MaxFrameRateプロパティを使用してカスタマイズ可能)。つまり、DispatcherTimerティックは1秒間に最大60回起動します。さらに、すべてのレンダリング作業はUIスレッドでも行われるため、前のポスターで指摘されているように、DispatcherTimerは描画が行われている速度で起動します。

3つのタイマーを追加することで、「データの追加」メソッドをイベントループごとに1回ではなく、3回実行するだけで、グラフがはるかに高速になっているように見えますが、実際にはフレームレートはおおよそです。同じ。1つのDispatcherTimerで同じ効果を得ることができ、各ティックに3倍のデータを追加するだけです。これを確認するには、CompositionTarget.Renderingイベントにフックし、そこでフレームレートを並行してカウントします。

以前に作成されたObservableCollectionポイントは優れたものですが、Visibloxにはその影響を軽減するためのちょっとした魔法があります。そのため、非常に高速でデータを追加する場合、グラフの更新は次の速度でバッチ処理されます。レンダリングループと不要な再レンダリングは削除されます。

また、IDataSeriesのObservableCollection実装に関連付けられることについてのあなたのポイントに関しては、たとえば単純なリストでバックアップすることにより、IDataSeriesインターフェースを自分で完全に自由に実装できます。明らかにそうすると、データが変更されたときにグラフが自動的に更新されなくなることに注意してください。Chart.Invalidate()を呼び出すか、手動で設定した軸範囲を変更することにより、チャートを強制的に更新できます。

于 2011-01-14T09:46:18.833 に答える