GUI何时重载?

时间:2012-07-03 22:34:21

标签: c# user-interface asynchronous dispatcher begininvoke

假设您永久使用

异步调用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:

我的程序的目的是在运行时观察特定的算法。我添加的字符串是日志条目:每次迭代一个日志字符串。我调用这些add-operations的原因是该算法在另一个线程中运行,而不是UI线程。为了赶上我不能从UI线程以外的任何线程进行UI操作的事实我构建了某种ThreadSafeObservableCollection(但我很确定这段代码不值得发布,因为它会减损实际问题什么我认为UI无法处理方法的重复和快速调用。

7 个答案:

答案 0 :(得分:2)

这很直截了当:当你超负荷用户的眼球时,你做错了。就现代cpu内核而言,这种情况发生得非常快,超过每秒20次更新,显示的信息看起来就像模糊。电影利用的东西,电影以每秒24帧的速度播放。

以更快的速度更新只是浪费资源。在UI线程开始弯曲之前,您仍然有大量的喘息空间。这取决于你要求它做的工作量,但典型的是x50安全边际。基于Environment.TickCount的简单计时器将完成工作,当差异> = 45毫秒时触发更新。

答案 1 :(得分:2)

经常向UI发布是一个红旗。这是另一种选择:将新字符串放入ConcurrentQueue并让计时器每隔100毫秒将它们拉出来。

非常简单易行,结果非常完美。

答案 2 :(得分: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.

也许你可以对你正在等待的代表人数做些什么......

答案 3 :(得分:1)

我没有使用WPF - 只是Windows Forms,但我建议如果只有一个只能查看的控件需要异步更新,那么正确的方法就是编写控件以便它可以从任何线程自由访问属性,只有当还没有更新待处理时才更新控件BeginInvoke刷新例程;后一个确定可以使用Int32“flag”和Interlock.Exchange(属性setter在更改底层字段后调用Interlocked.Exchange;如果标志已清除,则确实刷新例程中的BeginInvoke;刷新例程然后清除标志并执行刷新)。在某些情况下,可以通过让控件的刷新例程检查自上次运行以来经过了多长时间来进一步增强模式,如果答案小于20ms左右,则使用计时器在20ms后触发刷新前一个。

尽管.net可以处理在UI线程上发布的许多BeginInvoke个动作,但对于一次暂停的单个控件的更新通常是没有意义的。将待处理操作限制为每个控件一个(或最多一个小数字),并且不存在队列溢出的危险。

答案 4 :(得分:1)

要以更类似WPF的方式放置supercat的解决方案,尝试MVVM模式然后你可以有一个单独的视图模型类,你可以在线程之间共享,也许在适当的点上锁定或使用并发集合类。你实现了一个接口(我认为它是INotifyPropertyChanged并触发一个事件来说集合已经改变。这个事件必须从UI线程触发,但只需要

答案 5 :(得分:0)

在完成其他人提供的答案以及您对他们的评论后,您的实际意图似乎是确保UI保持响应。为此我认为你已经收到了很好的建议。

但是,为了回答你的问题(如何检测和标记UI线程的重载),我可以建议如下:

  1. 首先确定“重载”的定义应该是什么(例如我可以假设它是'UI线程停止渲染控件并停止处理用户输入'持续时间足够长)
  2. 定义此持续时间(例如,如果UI线程继续处理渲染并在最多40毫秒内输入消息,我会说它没有超载)。
  3. 现在根据您的重载定义(例如,可能是DispatcherPriority.Input或更低)设置DispatcherPriority设置DispactherTimer,并且Interval足够小于超载的“持续时间”
  4. 维护DateTime类型的共享变量,并在计时器的每个刻度上将其值更改为DateTime.Now。
  5. 在传递给BeginInvoke的委托中,您可以计算当前时间与上次Tick被触发之间的差异。如果它超出了你的'度量'的重载,那么UI线程根据你的定义是'Overloaded'。然后,您可以设置一个共享标志,可以从循环内部检查以采取适当的操作。
  6. 虽然我承认,这不是万无一失的,但通过实证调整你的“措施”,你应该能够在影响你之前发现超载。

答案 6 :(得分:0)

使用StopWatch来衡量最短,最长,平均,第一次和最后一次更新持续时间。 (您可以将其输出到您的UI。)

您的更新频率必须<&lt;比1 /(平均更新持续时间)。

更改算法的实现,以便多媒体计时器调用迭代,例如this .NET wrapperthis .NET wrapper。当计时器被激活时,使用Interlocked来防止在当前迭代完成之前运行新的迭代。如果需要在main上进行迭代,请使用调度程序。您可以为每个计时器事件运行多次迭代,为此使用参数,并与时间测量一起确定每个计时器事件运行的进程数以及您希望计时器事件的频率。

我建议不要使用小于5毫秒的定时器,因为定时器事件会使CPU窒息。

正如我在评论中写的更早,在调度到主线程时使用DispatcherPriority.Input,这样,UI的CPU时间不会被调度窒息。这与UI消息具有相同的优先级,因此不会忽略它们。