【问题标题】:Application.DoEvents vs await Task.Delay in a loopApplication.DoEvents 与 await Task.Delay 循环
【发布时间】:2016-02-03 21:28:23
【问题描述】:

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

【问题讨论】:

  • 这里有很多不使用 DoEvents() 的理由:stackoverflow.com/a/11352575/80274
  • 另外,您是否查看过this SO post 或许DocumentCompleted 没有提前触发,而是多次触发,而您只是听错了。
  • @ScottChamberlain 我已经阅读了您粘贴的两个链接。关于DocumentCompleted 的那个并不完全适用于我(是的,它确实触发了多次,不幸的是,每次触发时 URL 总是相同的,即使在最后一次触发之后,元素仍然不是我想要的状态它是)。至于DoEvents() 的危害,是的,已经阅读并理解了,但我的问题是Task.Delay() 与它有何不同。换句话说,它是否可能遭受与DoEvents() 相同的警告?它的工作方式有何不同?

标签: c# multithreading async-await task doevents


【解决方案1】:

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

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

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

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

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

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

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

【讨论】:

    【解决方案2】:

    await Task.Delay(10) 正在执行时,UI 可以处理事件。当Thread.Sleep(1); 运行时,UI 被冻结。所以await版本比较好,不会造成什么大问题。

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

    【讨论】:

    • 我知道你指出的缺点和差异,但我更感兴趣的是DoEvents() vs Task.Delay(),而不是Task.Delay() vs Thread.Sleep()
    • 我想另一件事也是......记住我经常调用await Task.Delay(10);(直到它返回true或达到超时)并且这将消耗一个线程(即使只是很短的时间)它将如何影响线程池?
    • 您有什么具体问题吗?我已经指出了两个代码 sn-ps 之间的所有差异,包括对 DoEvents 的调用。不确定要添加什么,因为您似乎知道所有基本问题。 DoEvents 本身没有不良副作用。这只是不良架构的标志。
    • 当延迟运行时,没有线程被使用。当延迟完成时,一个线程池线程将被占用大约一微秒。该线程向您的循环重新获得控制权的 UI 线程发出 BeginInvoke。所有这些都不会消耗任何资源。
    • 基本上有一个持续的论点认为 DoEvents() 是“更好的”,因为它不消耗线程池中的任何线程,在这种情况下,因为我正在等待一个看起来像正确的事情。我个人不持这种观点,但想听听别人的意见。
    【解决方案3】:

    我在这个上的 2 美分:

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

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

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

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

    【讨论】:

    • 确实如此,这可能会降低浏览器的速度。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-04
    • 1970-01-01
    • 2020-09-24
    • 2022-06-23
    相关资源
    最近更新 更多