AsyncAwaitSample
Framework for async/await and other concurrency experiments

AsyncAwaitSample is a collection of experiments around async/await feature of .NET. It comes with nicely formatted log that shows who did what when. It is extensible: you can esaily add your own experiments that answer your "what happens if" questions. Async/await has been a topic of many texts, but I strongly believe that experimental evidence is key to understanding any technology. You don't totally get it until you play with it and solve "What hapens if" riddles by actually trying them in code rather than by theoretical reasoning.

AsyncAwaitSample on GitHub

Download source code as ZIP

AsyncAwaitSample screenshot

Application Controls

The button panel on top is a list of available samples. Click on a sample to execute it. The text on a button corresponds to the name of a method in AsyncSample class, e.g. "AsyncVoid" runs AsyncSample.AsyncVoid().

The next row is occupied by the timer. If the timer is ticking, the UI thread has not been frozen. If the timer stops, the UI thread has been "hijacked" by a long running process and the user interface is frozen.

Below the timer is the log. It shows time of the event, the thread that logged the event and the log text. This log allows you to see what thread did what and when. Right click on the log and select "copy" to copy its contents to clipboard.

Samples

ShowTaskScheduler

Task scheduler is a confusing concept in .NET. At any particular moment you have

  • TaskScheduler.Default - thread pool task scheduler, used by Task.Run().
  • TaskScheduler.Current - task scheduler that will be used by Task.Start() by default.
  • TaskScheduler.FromCurrentSynchronizationContext() - task scheduler based on current SynchronizationContext.

The relationship between async/await, synchronization contexts and task schedulers is complex, and things can sometimes get quite hairy.

Important thing to remember is that await will start the task using current synchronization context (i.e. on UI thread if you await from the UI thread), but it does not change TaskScheduler.Default or TaskScheduler.Current.

23:41:40 [UI Thread] Click handle begin
23:41:40 [UI Thread] UI thread outside any task:
23:41:40 [UI Thread] 	TaskScheduler.Default: ThreadPoolTaskScheduler
23:41:40 [UI Thread] 	TaskScheduler.Current: ThreadPoolTaskScheduler
23:41:40 [UI Thread] 	TaskScheduler.FromCurrentSynchronizationContext(): SynchronizationContextTaskScheduler
23:41:40 [UI Thread] 	SynchronziationContext.Current: DispatcherSynchronizationContext
23:41:40 [Worker 03] Begin MyTask scheduled by Task.Run()
23:41:40 [Worker 03] 	TaskScheduler.Default: ThreadPoolTaskScheduler
23:41:40 [Worker 03] 	TaskScheduler.Current: ThreadPoolTaskScheduler
23:41:40 [Worker 03] 	TaskScheduler.FromCurrentSynchronizationContext(): Exception!
23:41:40 [Worker 03] 	SynchronziationContext.Current: null
23:41:40 [Worker 03] End MyTask scheduled by Task.Run()
23:41:40 [UI Thread] Click handle end
23:41:40 [UI Thread] Begin MyTask scheduled by await
23:41:40 [UI Thread] 	TaskScheduler.Default: ThreadPoolTaskScheduler
23:41:40 [UI Thread] 	TaskScheduler.Current: ThreadPoolTaskScheduler
23:41:40 [UI Thread] 	TaskScheduler.FromCurrentSynchronizationContext(): SynchronizationContextTaskScheduler
23:41:40 [UI Thread] 	SynchronziationContext.Current: DispatcherSynchronizationContext
23:41:40 [UI Thread] End MyTask scheduled by await

AsyncVoid

This sample demonstrates what happens when async void method is called. The method returns to caller when it encounters its first await clause, and the rest of the method is scheduled to run later. If the method is called on UI thread, all its parts are executed on UI thread.

public async void AsyncVoid()
{
    _logger.Log("AsyncVoid() begin");
    _logger.Log("await Task.Delay(3000)");
                    await Task.Delay(3000);
    _logger.Log("await returned");
    _logger.Log("AsyncVoid() end");
}

23:47:54 [UI Thread] Click handle begin
23:47:54 [UI Thread] AsyncVoid() begin
23:47:54 [UI Thread] await Task.Delay(3000)
23:47:54 [UI Thread] Click handle end
23:47:57 [UI Thread] await returned
23:47:57 [UI Thread] AsyncVoid() end

AsyncVoidWithSleep

When async void method is executed on the UI thread, Thread.Sleep() in any part of the method will block the UI thread. On the other hand, await Task.Delay(...) will not block the UI thread. Any blocking call or lots of CPU bound work will have the same effect as Thread.Sleep(). In the code below the timer freezes twice for 3 seconds:

public async void AsyncVoidWithSleep()
{
    _logger.Log("AsyncVoidWithSleep() begin"); 

    _logger.Log("Thread.Sleep(3000)");
    Thread.Sleep(3000);

    _logger.Log("await Task.Delay(3000)");
    await Task.Delay(3000);
    _logger.Log("await returned");

    _logger.Log("Thread.Sleep(3000)");
    Thread.Sleep(3000);

    _logger.Log("AsyncVoidWithSleep() end");
}

23:55:26 [UI Thread] Click handle begin
23:55:26 [UI Thread] AsyncVoidWithSleep() begin
23:55:26 [UI Thread] Thread.Sleep(3000)
-- UI thread freezes for 3 seconds
23:55:29 [UI Thread] await Task.Delay(3000)
23:55:29 [UI Thread] Click handle end
23:55:32 [UI Thread] await returned
23:55:32 [UI Thread] Thread.Sleep(3000)
-- UI thread freezes for 3 seconds
23:55:35 [UI Thread] AsyncVoidWithSleep() end

AwaitTaskWithSleep

Continuing on the previous theme: awaiting on a task that contains Thread.Sleep(), a blocking call or lots of CPU bound work will cause the UI thread to freeze.

public async void AwaitTaskWithSleep()
{
    _logger.Log("CreateTask() begin: calling await TaskWithSleep()");
    await TaskWithSleep();
    _logger.Log("CreateTask() end");
}

private async Task TaskWithSleep()
{
    await Task.Delay(100); // make asynchronous

    _logger.Log("TaskWithSleep() begin");
    for (int i = 0; i < 5; ++i)
    {
        _logger.Log("TaskWithSleep is hogging UI thread: " + i);  
        Thread.Sleep(1000);
    }
    _logger.Log("TaskWithSleep() end");
}

00:04:18 [UI Thread] Click handle begin
00:04:18 [UI Thread] CreateTask() begin: calling await TaskWithSleep()
00:04:18 [UI Thread] Click handle end
00:04:18 [UI Thread] TaskWithSleep() begin
-- UI thread freezes for 5 seconds
00:04:18 [UI Thread] TaskWithSleep is hogging UI thread: 0
00:04:19 [UI Thread] TaskWithSleep is hogging UI thread: 1
00:04:20 [UI Thread] TaskWithSleep is hogging UI thread: 2
00:04:21 [UI Thread] TaskWithSleep is hogging UI thread: 3
00:04:22 [UI Thread] TaskWithSleep is hogging UI thread: 4
00:04:23 [UI Thread] TaskWithSleep() end
00:04:23 [UI Thread] CreateTask() end

AsyncVoidAsyncException

We all heard that an exception thrown in an async void does not get propagated to the caller. Let's test it:

public async void AsyncVoidAsyncException()
{
    _logger.Log("AsyncVoidAsyncException() begin: calling Task.Delay()");
    await Task.Delay(3000);

    // "if" fools "unreachable code" detection in the compiler
    if (0 < Math.Sqrt(1)) throw new ApplicationException("AsyncVoidAsyncException Error"); 
    _logger.Log("AsyncVoidAsyncException() end");
}

00:11:26 [UI Thread] Click handle begin
00:11:26 [UI Thread] AsyncVoidAsyncException() begin: calling Task.Delay()
00:11:26 [UI Thread] Click handle end

As you can see, the caller ends normally, and exception is then thrown on the UI thread after 3 seconds (not visible in the log).

AsyncVoidSyncException

What happens if we throw exception in the synchronous part of an async void method, before the fisrt await?

public async void AsyncVoidSyncException()
{
    _logger.Log("AsyncVoidSyncException() begin: about to throw");

    // "if" fools "unreachable code" detection in the compiler
    if (0<Math.Sqrt(1)) throw new ApplicationException("AsyncVoidAsyncException Error");

    await Task.Delay(3000);
    _logger.Log("AsyncVoidSyncException() end");
}

00:14:16 [UI Thread] Click handle begin
00:14:16 [UI Thread] AsyncVoidSyncException() begin: about to throw
00:14:16 [UI Thread] Click handle end

As you can see, the method returns normally. The exception is scheduled to be thrown on the UI thread just like in the asynchronous case. Surprise!

Extending AsyncAwaitSample

To extend AsyncAwaitSample follow two simple steps:

  1. Add new method to AsyncSample class. Use _logger.Log() to output messages.
  2. Add your method to the Actions array in MainWindow constructor in MainWindow.xaml.cs.

That's it! You're going to get a new button named after your method. Clicking the button invokes the method. Use right click → Copy to copy the log to clipboard.

Feedback

Questions? Comments? Feel free to
Leave feedback