3

假设您永久地在 UI 线程/调度程序上异步调用一个方法

while (true) {

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

在程序的每次运行中,您都会观察到应用程序的 GUI 在大约 90 秒后由于调用的泛滥而开始冻结(时间会有所不同,但大致在 1 到 2 分钟之间)。

怎样才能准确地确定(测量?)这种过载发生的时间点以便足够早地停止它?

附录一:

在我的实际程序中,我没有无限循环。我有一个算法在终止之前迭代数百次。在每次迭代中,我都在 WPF 应用程序的 List 控件中添加一个字符串。我使用了 while (true) { ... } 构造,因为它最符合发生的情况。事实上,算法正确终止,所有(数百个)字符串都正确添加到我的列表中,但一段时间后,我失去了使用我的 GUI 的能力,直到算法终止 - 然后 GUI 再次响应。

附录二:

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

4

7 回答 7

2

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

比这更快的更新只是浪费资源。在 UI 线程开始崩溃之前,您还有很大的喘息空间。这取决于您要求它完成的工作量,但典型的是 x50 安全裕度。一个基于 Environment.TickCount 的简单计时器将完成工作,当差异 >= 45 毫秒时触发更新。

于 2012-07-03T23:44:43.133 回答
2

经常将其发布到 UI 是一个危险信号。这是另一种选择:将新字符串放入 aConcurrentQueue并让计时器每 100 毫秒将它们拉出。

非常简单易实现,结果很完美。

于 2012-07-03T23:53:40.203 回答
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 回答
1

我没有使用 WPF——只是 Windows 窗体,但我建议如果有一个需要异步更新的仅查看控件,正确的方法是编写控件,以便其属性可以是从任何线程自由访问,并且仅当还没有更新挂起时BeginInvoke更新控件才会刷新例程;后一种确定可以使用“标志”和(属性设置器在更改基础字段后调用标志;如果标志已被清除,它会执行Int32Interlock.ExchangeInterlocked.ExchangeBeginInvoke关于刷新程序;然后刷新例程清除标志并执行刷新)。在某些情况下,可以通过让控件的刷新例程检查自上次运行以来经过了多少时间来进一步增强模式,如果答案小于 20 毫秒左右,则使用计时器在 20 毫秒后触发刷新前一个。

尽管 .net 可以处理BeginInvoke在 UI 线程上发布的许多操作,但一次只为单个控件进行更新通常是没有意义的。将待处理的操作限制为每个控件一个(或最多一个少量),并且不会有队列溢出的危险。

于 2012-07-03T23:23:48.287 回答
1

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

于 2012-07-03T23:53:43.973 回答
0

After going through the answers provided by others and your comments on them, your actual intent seems to be ensuring that UI remains responsive. For this I think you have already received good proposals.

But still, to answer your question (how to detect and flag overloading of UI thread) verbatim, I can suggest the following:

  1. First determine what should be the definition of 'overloading' (for e.g. I can assume it to be 'UI thread stops rendering the controls and stops processing user input' for a big enough duration)
  2. Define this duration (for e.g. if UI thread continues to process render and input messages in at-most 40ms I will say it is not overloaded).
  3. Now Initiate a DispactherTimer with DispatcherPriority set according to your definition for overloading (for my e.g. it can be DispatcherPriority.Input or lower) and Interval sufficiently less than your 'duration' for overloading
  4. Maintain a shared variable of type DateTime and on each tick of the timer change its value to DateTime.Now.
  5. In the delegate you pass to BeginInvoke, you can compute a difference between current time and the last time Tick was fired. If it exceeds your 'measure' of overloading then well the UI thread is 'Overloaded' according to your definition. You can then set a shared flag which can be checked from inside your loop to take appropriate action.

Though I admit, it is not fool proof, but by empirically adjusting your 'measure' you should be able to detect overloading before it impacts you.

于 2012-07-04T06:34:31.900 回答
0

使用秒表测量最小、最大、平均、第一次和最后一次更新持续时间。(您可以将其输出到您的 UI。)

您的更新频率必须小于 1/(平均更新持续时间)。

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

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

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

于 2012-07-04T09:30:18.070 回答