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.
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 byTask.Run()
.TaskScheduler.Current
- task scheduler that will be used byTask.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:
- Add new method to
AsyncSample
class. Use_logger.Log()
to output messages. - Add your method to the
Actions
array inMainWindow
constructor inMainWindow.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
Copyright (c) Ivan Krivyakov. Last updated: May 30, 2015