Application.DoEvents vs await Task.Delay in a loop

為{幸葍}努か 提交于 2019-12-22 10:56:40

问题


Much to my discontent, I need to use a WebBrowser control in one of my apps.

One of the things I also need to do is wait for an element to become visible/class changes/etc, which happens well after the DocumentCompleted event is fired, making the event close to useless in my case.

So currently I have something like...

while (webBrowser.Document?.GetElementById("id")?.GetAttribute("classname") != "class")
{
    Application.DoEvents();
    Thread.Sleep(1);
}

Now I've read in multiple places that DoEvents() is evil and can cause lots of issues, so I thought about replacing it with Task.Delay() as such:

while (webBrowser.Document?.GetElementById("id")?.GetAttribute("classname") != "class")
{
    await Task.Delay(10);
}

So my question is, other than the obvious facts that the Thread.Sleep() will block events for 1ms and that the Task.Delay() has a bigger delay set in the example above, what are the actual differences between doing the two approaches, which is better and why?

PS: Please stick to the question, while I wouldn't necessarily mind other ideas on how to fix the WebBrowser control issue itself by using something else (js injection comes to mind), this is not the place to answer that, this question is about how these two bits of code differ and which would be considered better.


回答1:


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

The differences are in how messages are processed while waiting.

DoEvents will install a nested message loop; this means that your stack will have (at least) two message processing loops. This 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 blog post.

In contrast, await will return. So it doesn't use a nested message loop to process messages; it just allows the original message loop to process them. When the async method is ready to resume, it will send a special message to the message loop that resumes executing the async method.

So, await enables concurrency but without all the really difficult reentrancy concerns inherent in DoEvents. await is definitely the superior approach.

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

Well, await doesn't consume any threads from the thread pool, either. Boom.




回答2:


While await Task.Delay(10) is executing the UI can process events. While Thread.Sleep(1); is running the UI is frozen. So the await version is better and will not cause any major issues.

Clearly, spinning in a busy loop has the risk of burning CPU and battery. You seem to be aware of those drawbacks.




回答3:


My 2 cents on this one:

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.

Broadly speaking, the WebBrowser control needs the UI thread to do some bits of its parsing/rendering job. By spinning a busy-waiting loop like yours with Application.DoEvents(); Thread.Sleep(1) on the UI thread, you essentially make this thread less available to other UI controls (including the WebBrowser itself). So, the polling operation from you example will most likely take longer to finish, than when you use an asynchronous loop with Task.Delay.

Moreover, the use of the thread pool by Task.Delay is not an issue here. A thread pool thread gets engaged here for a very short time, only to post an await completion message to the synchronization context of the main thread. That's because you don't use ConfigureAwait(false) here (correctly in this case, as you indeed need to do you polling logic on the UI thread).

If nevertheless you're still concerned about the use the thread pool, you can easily replicate Task.Delay with System.Windows.Forms.Timer class (which uses a low-priority WM_TIMER message) and TaskCompletionSource (to turn the Timer.Tick even into an awaitable task).



来源:https://stackoverflow.com/questions/35188070/application-doevents-vs-await-task-delay-in-a-loop

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!