Async/Await - is it *concurrent*?

12,015

Solution 1

It is concurrent, in the sense that many outstanding asychronous operations may be in progress at any time. It may or may not be multithreaded.

By default, await will schedule the continuation back to the "current execution context". The "current execution context" is defined as SynchronizationContext.Current if it is non-null, or TaskScheduler.Current if there's no SynchronizationContext.

You can override this default behavior by calling ConfigureAwait and passing false for the continueOnCapturedContext parameter. In that case, the continuation will not be scheduled back to that execution context. This usually means it will be run on a threadpool thread.

Unless you're writing library code, the default behavior is exactly what's desired. WinForms, WPF, and Silverlight (i.e., all the UI frameworks) supply a SynchronizationContext, so the continuation executes on the UI thread (and can safely access UI objects). ASP.NET also supplies a SynchronizationContext that ensures the continuation executes in the correct request context.

Other threads (including threadpool threads, Thread, and BackgroundWorker) do not supply a SynchronizationContext. So Console apps and Win32 services by default do not have a SynchronizationContext at all. In this situation, continuations execute on threadpool threads. This is why Console app demos using await/async include a call to Console.ReadLine/ReadKey or do a blocking Wait on a Task.

If you find yourself needing a SynchronizationContext, you can use AsyncContext from my Nito.AsyncEx library; it basically just provides an async-compatible "main loop" with a SynchronizationContext. I find it useful for Console apps and unit tests (VS2012 now has built-in support for async Task unit tests).

For more information about SynchronizationContext, see my Feb MSDN article.

At no time is DoEvents or an equivalent called; rather, control flow returns all the way out, and the continuation (the rest of the function) is scheduled to be run later. This is a much cleaner solution because it doesn't cause reentrancy issues like you would have if DoEvents was used.

Solution 2

The whole idea behind async/await is that it performs continuation passing nicely, and doesn't allocate a new thread for the operation. The continuation may occur on a new thread, it may continue on the same thread.

Solution 3

The real "meat" (the asynchronous) part of async/await is normally done separately and the communication to the caller is done through TaskCompletionSource. As written here http://blogs.msdn.com/b/pfxteam/archive/2009/06/02/9685804.aspx

The TaskCompletionSource type serves two related purposes, both alluded to by its name: it is a source for creating a task, and the source for that task’s completion. In essence, a TaskCompletionSource acts as the producer for a Task and its completion.

and the example is quite clear:

public static Task<T> RunAsync<T>(Func<T> function) 
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        {  
            T result = function(); 
            tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
    }); 
    return tcs.Task; 
}

Through the TaskCompletionSource you have access to a Task object that you can await, but it isn't through the async/await keywords that you created the multithreading.

Note that when many "slow" functions will be converted to the async/await syntax, you won't need to use TaskCompletionSource very much. They'll use it internally (but in the end somewhere there must be a TaskCompletionSource to have an asynchronous result)

Solution 4

I feel like this question needs a simpler answer for people. So I'm going to oversimplify.

The fact is, if you save the Tasks and don't await them, then async/await is "concurrent".

var a = await LongTask1(x);
var b = await LongTask2(y);

var c = ShortTask(a, b);

is not concurrent. LongTask1 will complete before LongTask2 starts.

var a = LongTask1(x);
var b = LongTask2(y);

var c = ShortTask(await a, await b);

is concurrent.

While I also urge people to get a deeper understanding and read up on this, you can use async/await for concurrency, and it's pretty simple.

Solution 5

The way I like to explain it is that the "await" keyword simply waits for a task to finish but yields execution to the calling thread while it waits. It then returns the result of the Task and continues from the statement after the "await" keyword once the Task is complete.

Some people I have noticed seem to think that the Task is run in the same thread as the calling thread, this is incorrect and can be proved by trying to alter a Windows.Forms GUI element within the method that await calls. However, the continuation is run in the calling thread where ever possible.

Its just a neat way of not having to have callback delegates or event handlers for when the Task completes.

Share:
12,015
Neil Barnwell
Author by

Neil Barnwell

I'm an Application Developer and Software Architect for a wide variety of software solutions including websites and desktop applications. Most recently focussed on a bespoke warehouse management system using C# 3.5 and SQL Server 2008. Since my move to .NET I've become active in the community, attending monthly usergroup meetings and various conferences. I've even made a foray into speaking at usergroups about topics I am passionate about. While I love experimenting with new tech and have a hobby project hosted on CodePlex, my current focus is less on specific new technologies and more on good principles and techniques. When not at work I'm a family man, biker, amateur photographer, guitarist and of course, software developer.

Updated on June 06, 2022

Comments

  • Neil Barnwell
    Neil Barnwell almost 2 years

    I've been considering the new async stuff in C# 5, and one particular question came up.

    I understand that the await keyword is a neat compiler trick/syntactic sugar to implement continuation passing, where the remainder of the method is broken up into Task objects and queued-up to be run in order, but where control is returned to the calling method.

    My problem is that I've heard that currently this is all on a single thread. Does this mean that this async stuff is really just a way of turning continuation code into Task objects and then calling Application.DoEvents() after each task completes before starting the next one?

    Or am I missing something? (This part of the question is rhetorical - I'm fully aware I'm missing something :) )