6

令我非常不满的是,我需要在我的一个应用程序中使用 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注入),这不是回答的地方,这个问题是关于这两位代码有何不同,哪些会被认为更好。

4

3 回答 3

10

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

不同之处在于等待时如何处理消息。

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

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

因此,await启用并发,但没有DoEvents. await绝对是上乘的方法。

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

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

于 2016-02-04T14:49:41.087 回答
3

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

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

于 2016-02-03T21:46:49.957 回答
1

我在这个上的 2 美分:

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

从广义上讲,WebBrowser控件需要 UI 线程来完成一些解析/渲染工作。通过 Application.DoEvents(); Thread.Sleep(1)在 UI 线程上旋转像您这样的忙等待循环,您实际上使该线程对其他 UI 控件(包括其本身)的可用性降低。WebBrowser因此,您示例中的轮询操作很可能需要更长的时间才能完成,而不是使用异步循环Task.Delay

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

如果您仍然担心线程池的使用,您可以轻松地Task.Delay使用System.Windows.Forms.Timer类(使用低优先级WM_TIMER消息)和TaskCompletionSource(将Timer.Tick偶数变成等待任务)进行复制。

于 2016-02-03T23:42:26.200 回答