There is a strong emphasis that async/await is unrelated to multi-threading in most tutorials; that a single thread can dispatch multiple I/O operations and then handle the results as they complete without creating new threads. The concept makes sense but I've never seen that actual behavior in practice.
Take the below example:
static void Main(string[] args)
{
// No Delay
// var tasks = new List<int> { 3, 2, 1 }.Select(x => DelayedResult(x, 0));
// Staggered delay
// var tasks = new List<int> { 3, 2, 1 }.Select(x => DelayedResult(x, x));
// Simultaneous Delay
// var tasks = new List<int> { 3, 2, 1 }.Select(x => DelayedResult(x, 1));
var allTasks = Task.WhenAll(tasks);
allTasks.Wait();
Console.ReadLine();
}
static async Task<T> DelayedResult<T>(T result, int seconds = 0)
{
ThreadPrint("Yield:" + result);
await Task.Delay(TimeSpan.FromSeconds(seconds));
ThreadPrint("Continuation:" + result);
return result;
}
static void ThreadPrint(string message)
{
int threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Thread:" + threadId + "|" + message);
}
"No Delay" uses only one thread and executes the continuation immediately as though it were synchronous code. Looks good.
Thread:1|Yield:3
Thread:1|Continuation:3
Thread:1|Yield:2
Thread:1|Continuation:2
Thread:1|Yield:1
Thread:1|Continuation:1
"Staggered Delay" uses two threads. We have left the single-threaded world behind and there are absolutely new threads being created in the thread pool. At least the thread used for processing the continuations is reused and processing occurs in the order completed rather than the order invoked.
Thread:1|Yield:3
Thread:1|Yield:2
Thread:1|Yield:1
Thread:4|Continuation:1
Thread:4|Continuation:2
Thread:4|Continuation:3
"Simultaneous Delay" uses...4 threads! This is no better than regular old multi-threading; in fact, its worse since there is an ugly state machine hiding under the covers in the IL.
Thread:1|Yield:3
Thread:1|Yield:2
Thread:1|Yield:1
Thread:4|Continuation:1
Thread:7|Continuation:3
Thread:5|Continuation:2
Please provide a code example for the "Simultaneous Delay" that only uses one thread. I suspect there isn't one...which begs the question of why the async/await pattern is advertised as unrelated to multi-threading when it clearly either a) uses the ThreadPool
and dispatches new threads as necessary or b) in a UI or ASP.NET context, simply deadlocks on a single thread unless you await "all the way up" which just means that the magic additional thread is being handled by the framework (not that it does not exist).
IMHO, async/await is an awesome abstraction for using continuations everywhere for high availability without getting mired in callback hell...but let's not pretend we are somehow dodging multi-threading. What am I missing?