Application.DoEvents vs等待循环

时间:2016-02-03 21:28:24

标签: c# multithreading async-await task doevents

令我不满的是,我需要在我的一个应用程序中使用WebBrowser控件。

我还需要做的一件事就是等待一个元素变为可见/类更改/等,这在DocumentCompleted事件被触发后很好地发生,使得事件在我的情况下接近无用。

所以目前我有类似......

while (webBrowser.Document?.GetElementById("id")?.GetAttribute("classname") != "class")
{
    Application.DoEvents();
    Thread.Sleep(1);
}

现在我已经在多个地方读到DoEvents()是邪恶的并且可能会导致很多问题,所以我考虑用Task.Delay()替换它:

while (webBrowser.Document?.GetElementById("id")?.GetAttribute("classname") != "class")
{
    await Task.Delay(10);
}

所以我的问题是,除了明显的事实,Thread.Sleep()将阻止1ms的事件,并且Task.Delay()在上面的示例中设置了更大的延迟,实际的差异是什么这两种方法哪个更好,为什么?

PS:请坚持这个问题,而我不一定会想到如何通过使用其他东西解决WebBrowser控制问题本身的其他想法(js注入来了记住,这不是回答这个问题的地方,这个问题是关于这两个代码如何不同以及哪个代码会更好。

3 个答案:

答案 0 :(得分:6)

  

两种方法之间的实际差异是什么,这更好,为什么?

区别在于等待时如何处理消息。

DoEvents将安装嵌套的消息循环;这意味着您的堆栈将具有(至少)两个消息处理循环。这是causes reentrancy issues,IMO是避免DoEvents的最大理由。关于嵌套消息循环应该处理哪些事件的问题是无休止的,因为该决定双方都存在死锁,而且没有适用于所有应用程序的解决方案。有关消息抽取的深入讨论,请参阅经典的Apartments and Pumping in the CLR博客文章。

相反,await返回。所以它没有使用嵌套的消息循环来处理消息;它只允许原始消息循环来处理它们。当async方法准备好恢复时,它将向消息循环发送一条特殊消息,该消息循环将继续执行async方法。

因此,await可以实现并发,但没有DoEvents中固有的所有非常困难的重入问题。 await绝对是一种优越的方法。

  

基本上有一个持续的论点,即DoEvents()更好"因为它不会消耗线程池中的任何线程

好吧,await也不会消耗线程池中的任何线程。吊杆。

答案 1 :(得分:2)

await Task.Delay(10)正在执行时,UI可以处理事件。当Thread.Sleep(1);正在运行时,UI被冻结。因此await版本更好,不会导致任何重大问题。

显然,在繁忙的循环中旋转会有燃烧CPU和电池的风险。你似乎意识到了这些缺点。

答案 2 :(得分:1)

这个我的2美分:

  

基本上有一个持续的论点,即DoEvents()是“更好”   因为它不会消耗线程池中的任何线程   在这种情况下,因为我正在等待一个看似正确的控件   要做的事。

从广义上讲,WebBrowser控件需要UI线程来完成其解析/渲染作业的一些部分。通过在UI线程上使用Application.DoEvents(); Thread.Sleep(1)旋转像您这样的繁忙等待循环,您基本上可以使该线程 less 可用于其他UI控件(包括WebBrowser本身)。因此,与您使用Task.Delay的异步循环相比,示例的轮询操作很可能需要更长时间才能完成

此外,Task.Delay使用线程池不是问题。线程池线程在这里使用很短的时间,只是将await完成消息发布到主线程的同步上下文。那是因为你不在这里使用ConfigureAwait(false)(在这种情况下正确,因为你确实需要在UI线程上轮询逻辑)。

如果您仍然担心使用线程池,则可以轻松地使用Task.Delay类(使用low-priority WM_TIMER message)和System.Windows.Forms.Timer复制TaskCompletionSource(把Timer.Tick变成一个等待的任务。)

相关问题