8

对于一个对你们所有人来说可能很简单的问题,我再次表示歉意。我对 Silverlight 的幕后情况了解有限。

我有一个图表应用程序 (Visiblox),我用作滚动范围,每 20 毫秒更新一次,添加和删除一个点。在伪代码中:

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 系列时,它开始显示有点迟缓。将刻度更改为 10 毫秒没有任何区别,图表以相同的速度更新,因此似乎 20 毫秒是速度限制(UI 或图表?)。

我尝试CompositionTarget.Rendering并得到了相同的结果:低于 20 毫秒,速度没有差异。

然后我不小心启用了两者,速度翻了一番。所以我用多个线程(2、3、4)进行了测试,速度增加了一倍、三倍和四倍。这还没有锁,因为我什至不知道我需要在哪个进程上生成锁,但没有数据损坏或内存泄漏。

我的问题是为什么 20ms 的缓慢图表不能在 10ms 运行,但在多线程时却快得离谱?UI 刷新过程是否运行得更快?图表计算是否加倍?或者单个 DispatcherTimer 的执行速度是否有限制?

谢谢!


编辑:我有嵌入式编码的背景,所以当我想到线程和时序时,我立即想到在硬件中切换一个引脚并连接一个示波器来测量进程长度。我是 C# 中的线程新手,并且没有用于连接范围的引脚。有没有办法以图形方式查看线程计时?

4

2 回答 2

7

在 UI 线程上触发其 Tick 事件的 DispatcherTimer 被认为是低分辨率或低精度计时器,因为它的 Interval 实际上意味着“自上次滴答以来不早于 x 滴答”。如果 UI 线程忙于做任何事情(处理输入、刷新图表等),那么它将延迟计时器的事件。此外,在 UI 线程上以非常短的时间间隔让一堆 DispatcherTimer 滴答作响也会减慢应用程序的响应速度,因为在引发 Tick 事件时,应用程序无法响应输入。

因此,正如您所指出的,为了频繁处理数据,您应该移至后台线程。但有一些警告。您当前没有观察到损坏或其他错误的事实可能纯属巧合。如果在前台线程尝试读取列表的同时在后台线程上修改列表,您最终会崩溃(如果幸运的话)或看到损坏的数据。

在您的示例中,您有一条评论说“无需刷新图表,它会自动刷新”。这让我想知道图表如何知道您更改了datapoints集合?List<T>修改时不会引发事件。如果您使用的是,ObservableCollection<T>我会指出,每次删除/添加一个点时,您都​​可能会刷新图表,这可能会减慢速度。

但是,如果您实际上正在使用List<T>,那么肯定有其他东西(也许是另一个计时器?)正在刷新图表。也许图表控件本身具有内置的自动刷新机制?

无论如何,这个问题有点棘手,但不是全新的。您可以通过多种方式在后台线程上维护集合并从 UI 线程绑定到它。但是您的 UI 刷新速度越快,您等待后台线程释放锁的可能性就越大。

减少这种情况的一种方法是使用 aLinkedList<T>而不是List<T>. 添加到 LinkedList 的末尾是 O(1),删除一个项目也是如此。List<T>当您从一开始删除一个项目时,A需要将所有内容向下移动。通过使用 LinkedList,您可以在后台线程中锁定它,您将最大限度地减少您持有锁定的时间。在 UI 线程上,您还需要获得相同的锁并将列表复制到数组或在持有锁时刷新图表。

另一种可能的解决方案是在后台线程上缓冲点的“块”,并使用 Dispatcher.BeginInvoke 将它们中的一批发布到 UI 线程,然后您可以安全地更新集合。

于 2011-01-12T01:59:20.460 回答
4

我认为这里的关键是要意识到 Silverlight 默认以 60fps 的最大帧速率渲染(可通过您的 MaxFrameRate 属性自定义)。这意味着 DispatcherTimer 滴答每秒最多触发 60 次。此外,所有渲染工作也发生在 UI 线程上,因此 DispatcherTimer 以最好的绘图发生率触发,正如之前的海报所指出的那样。

您通过添加三个计时器所做的结果只是在每个事件循环中触发“添加数据”方法 3 次而不是一次,因此看起来您的图表运行速度要快得多,但实际上帧速率大约是相同。您可以使用单个 DispatcherTimer 获得相同的效果,并且只需在每个 Tick 上添加 3 倍的数据。您可以通过挂钩 CompositionTarget.Rendering 事件并在那里并行计算帧速率来验证这一点。

之前创建的 ObservableCollection 点是一个很好的点,但在Visiblox中有一些魔法可以尝试减轻其影响,因此如果您以非常快的速度添加数据,图表更新将以渲染循环和不必要的重新渲染将被丢弃。

Also regarding your point about being tied to the ObservableCollection implementation of IDataSeries, you are entirely free to implement the IDataSeries interface yourself, for example by backing it with a simple List. Just be aware that obviously if you do that the chart will no longer automatically update when data changes. You can force a chart update by calling Chart.Invalidate() or by changing a manually set axis range.

于 2011-01-14T09:46:18.833 回答