Application.DoEvents vs await Task.Delay in a loop

本文关键字:in loop Delay Task DoEvents vs await Application | 更新日期: 2023-09-27 18:33:10

令我不满的是,我需要在我的一个应用程序中使用 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()将阻止事件 1 毫秒并且Task.Delay()在上面示例中设置的延迟更大的明显事实之外,执行这两种方法之间的实际区别是什么,哪个更好,为什么?

PS:请坚持这个问题,虽然我不一定介意关于如何使用其他东西来解决WebBrowser控制问题本身的其他想法(想到js注入(,但这不是回答这个问题的地方,这个问题是关于这两段代码有何不同,哪个会被认为更好。

Application.DoEvents vs await Task.Delay in a loop

做这两种方法之间的实际区别是什么,哪个更好,为什么?

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

DoEvents将安装一个嵌套的消息循环;这意味着您的堆栈将(至少(有两个消息处理循环。这会导致重入问题,IMO是避免DoEvents的最大原因。关于嵌套消息循环应该处理哪种类型的事件,有无穷无尽的问题,因为该决策的双方都存在死锁,并且没有适合所有应用程序的解决方案。有关消息抽取的深入讨论,请参阅 CLR 博客文章中的经典公寓和抽取。

相比之下,await回来。因此,它不使用嵌套消息循环来处理消息;它只允许原始消息循环处理它们。当 async 方法准备好恢复时,它将向恢复执行 async 方法的消息循环发送一条特殊消息。

因此,await支持并发性,但没有DoEvents固有的所有真正困难的重入问题。 await绝对是更好的方法。

基本上,有一个

持续的争论认为DoEvents((是"更好的",因为它不消耗线程池中的任何线程。

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

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

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

我在这个问题上的 2 美分:

基本上,有一个持续的争论,即DoEvents((是"更好的"。 因为它不消耗线程池中的任何线程,并且 在这种情况下,因为我正在等待一个看起来正确的控件 要做的事情。

从广义上讲,WebBrowser控件需要 UI 线程来完成其分析/呈现作业的某些位。通过在 UI 线程上使用 Application.DoEvents(); Thread.Sleep(1) 旋转像您这样的繁忙等待循环,实质上会使此线程对其他 UI 控件(包括WebBrowser本身(的可用性降低。因此,与将异步循环与 Task.Delay 一起使用时相比,示例中的轮询操作很可能需要更长的时间才能完成

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

但是,如果您仍然担心线程池的使用,则可以轻松地使用System.Windows.Forms.Timer类(使用低优先级WM_TIMER消息(和TaskCompletionSource(将Timer.Tick转换为可等待的任务(复制Task.Delay