Application.DoEvents vs await Task.Delay 循环

Application.DoEvents vs await Task.Delay in a loop

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

我还需要做的一件事是等待元素变为 visible/class changes/etc,这发生在 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 注入),这不是回答这个问题的地方,这个问题是关于这两个代码有何不同以及哪个会被认为更好。

虽然 await Task.Delay(10) 正在执行 UI 可以处理事件。虽然 Thread.Sleep(1); 是 运行,但 UI 是冻结的。所以 await 版本更好,不会造成任何重大问题。

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

我在这个上的 2 美分:

Basically there's an ongoing argument that DoEvents() is "better" because it doesn't consume any threads from the thread pool and that in this case since I'm waiting for a control that seems like the right thing to do.

一般来说,WebBrowser 控件需要 UI 线程来完成它的一些 parsing/rendering 工作。通过在 UI 线程上使用 Application.DoEvents(); Thread.Sleep(1) 旋转一个忙等待循环,您实际上使该线程 less 可用于其他 UI 控件(包括 WebBrowser 本身)。因此,与使用 Task.Delay.

的异步循环相比,您示例 的轮询操作很可能需要更长的时间才能完成

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

如果您仍然担心线程池的使用,您可以使用 System.Windows.Forms.Timer class(使用 low-priority WM_TIMER message)轻松复制 Task.DelayTaskCompletionSource(将 Timer.Tick 甚至变成可等待的任务)。

what are the actual differences between doing the two approaches, which is better and why?

不同之处在于消息在等待时的处理方式。

DoEvents 将安装一个嵌套的消息循环;这意味着您的堆栈将(至少)有两个消息处理循环。此 causes reentrancy issues, which IMO is the biggest reason to avoid DoEvents. There's endless questions about which kinds of events the nested message loop should process, because there's deadlocks on both sides of that decision, and there's no solution that's right for all applications. For an in-depth discussion of message pumping, see the classic Apartments and Pumping in the CLR 博客 post.

相比之下,awaitreturn。所以它不使用嵌套的消息循环来处理消息;它只允许 original 消息循环来处理它们。当 async 方法准备好恢复时,它会向恢复执行 async 方法的消息循环发送一条特殊消息。

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

Basically there's an ongoing argument that DoEvents() is "better" because it doesn't consume any threads from the thread pool

好吧,await 也不使用线程池中的任何线程。轰.