Closed Timovzl closed 3 years ago
From reading the code, it looks like TaskUtil.Await
begins executing its work on the current thread, but with a SynchronizationContext
that passes any continuations off to a thread pool thread. The original thread is then blocked (Thread.Sleep
in a loop) until the work completes running on thread pool thread(s).
AsyncContext.Run
also takes over the current thread until the work is complete, but it works by scheduling all continuations onto that same thread, not on thread pool threads.
AFAICT that's the main difference.
Your reponse is much appreciated, @StephenCleary!
Do you expect any performance difference between Thread.Sleep(3)
in a loop vs. GetAwaiter().GetResult()
?
P.S. MassTransit's implementation seems to use a single thread as well, judging by its SingleThreadSynchronizationContext
.
If you need to block, then blocking is generally more performant than busy waiting. I suspect (but am not sure) that they may be doing busy waiting in case the original thread is a UI thread, in which case busy waiting enables STA pumping. IIRC pumping might be done by the Thread.Sleep
call.
SingleThreadSynchronizationContext
uses a ChannelExecutor
bounded to a single item at a time. ChannelExecutor
runs its consumer task on a thread pool thread - and the consumer is asynchronous, so it may switch threads at any await
. Finally, the actual continuations themselves are also wrapped in a thread pool thread.
So, I haven't actually run the code, but I'm quite sure that SingleThreadExecutionContext
only ensures that one continuation at a time may be run, and allows them to run on any available thread pool thread.
Although this question does not concern only AsyncEx, I thought this would be the most reliable place to ask.
Where AsyncEx provides AsyncContext.Run, the well-known MassTransit package happens to provide TaskUtil.Await. The intent seems to be largely the same.
Both methods seem to succeed in avoiding most deadlocks. If I'm not mistaken, both also help avoid thread pool starvation (by letting only one thread pick up the continuations?).
Still, the implementations are quite different. (For one thing,
TaskUtil.Await
usesThread.Sleep
in a loop.) I'm trying to understand the consequences of the differences, but I'm not familiar enough with the concepts.I'm curious what practical consequences I should expected when using either of the methods.