令我不满的是,我需要在我的一个应用程序中使用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注入来了记住,这不是回答这个问题的地方,这个问题是关于这两个代码如何不同以及哪个代码会更好。
答案 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
变成一个等待的任务。)