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注入(,但这不是回答这个问题的地方,这个问题是关于这两段代码有何不同,哪个会被认为更好。
做这两种方法之间的实际区别是什么,哪个更好,为什么?
区别在于等待时如何处理消息。
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
。