问题
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