12

Let me just post a simple example:

    private void MyMethod()
    {
        Task task = MyAsyncMethod();
        task.Wait();
    }

    private async Task MyAsyncMethod()
    {
        //Code before await
        await MyOtherAsyncMethod();
        //Code after await
    }

Let's say I run the above code in a single threaded app -like a console app-. I'm having a difficult time understanding how the code //Code after await would be able to run.

I understand that when I hit the await keyword in MyAsyncMethod() control goes back to MyMethod(), but then I'm locking the thread with task.Wait(). If the thread is locked, how can //Code after await ever run if the thread that is supposed to take it is locked?

Does a new thread get created to run //Code after await? Or does the main thread magically steps out of task.Wait() to run //Code after await?

I'm not sure how this is supposed to work?

AxiomaticNexus
  • 6,190
  • 3
  • 41
  • 61

2 Answers2

15

Code as posted will "Deadlock" in Winform App if called from main thread because you're blocking the main thread with the Wait().

But in console app this works. but how?

Answer is hidden in the SynchronizationContext.Current. await captures the "SynchronizationContext" and when the task is completed it will continue in the same "SynchronizationContext".

In winform app SynchronizationContext.Current will be set to WindowsFormsSynchronizationContext which will post to the call to "Message loop", but who is going to process that? out main thread is waiting in Wait().

In console app SynchronizationContext.Current will not be set by default so it will be null when no "SynchronizationContext" available for await to capture so it will schedule the continuation to ThreadPool(TaskScheduler.Default which is ThreadpoolTaskScheduler) and so the code after await works(through threadpool thread).

Aforementioned capturing behavior can be controlled using Task.ConfigureAwait(false); which will prevent winform app from deadlocking but code after await no longer runs in UI thread.

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • This all assumes that the current synchronization context is never altered by user code; you can set it in any of those applications to whatever you want, and give it whatever behavior you want. – Servy Oct 23 '13 at 14:06
  • I had no idea about thread pools behind .Net console apps. Being aware of that now answers everything. Thank you. – AxiomaticNexus Oct 23 '13 at 14:08
  • 1
    @YasmaniLlanes If you don't have idea about "ThreadPool" I'd suggest you to read about it. It should be this way learn ThreadPool first then TPL and then async/await. – Sriram Sakthivel Oct 23 '13 at 14:11
  • so after ``await`` keyword,on next line whatever code is, will be executed in the calling thread means UI thread ? – Ehsan Sajjad Feb 12 '16 at 20:55
  • How can this deadlock? Doesn't it just freeze until task.Wait() finishes? – user764754 Mar 16 '16 at 15:53
  • 1
    @user764754 I'm too lazy at the moment. I'll share you a blog post which explains why it deadlocks. Refer http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html – Sriram Sakthivel Mar 16 '16 at 16:20
14

Does a new thread get created to run //Code after await?

Maybe. Maybe not. The awaitable pattern implementation for Task runs the continuation (the bit after the await expression) using the synchronization context which was "current" when at the start of the await expression. If you're in the context of a UI thread, for example, that means that you'll end up back on the same UI thread. If you're in a thread-pool thread, you'll end up back on some thread-pool thread, but it could be a different one.

Of course with your code sample, if you're in a UI thread, your call to Wait() will block the UI thread so that the continuation can't run - you need to be careful about that. (Calling Wait() or Result on tasks that you don't know to be completed, and which may require work on the current thread, is a bad idea.)

Note that you can call Task.ConfigureAwait so that you can express the intention of not requiring to continue on the same context. This is usually appropriate for library methods which don't care which thread they run on:

await task.ConfigureAwait(false);

(It affects more than the thread - it's the whole context which is captured or not.)

I think it's a good idea to get familiar with what's going on under the hood with await. There's plenty of documentation online, and if you'll allow me a brief plug, there's also the 3rd edition of C# in Depth, and my Tekpub screencast series on the topic. Or start with MSDN and proceed from there.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Alright, I understand the behavior in the context of a UI thread like in a WCF app or thread pool like in a web app? What happens in a single threaded app like a console app? – AxiomaticNexus Oct 23 '13 at 13:50
  • 1
    @Jon: The continuation is actually queued to the current `SynchronizationContext`, falling back on the current `TaskScheduler`. This is because `TaskScheduler.Current` is all quirky and not very intuitive. – Stephen Cleary Oct 23 '13 at 13:51
  • 2
    @YasmaniLlanes: There's actually no such thing as a single-threaded .NET app; every .NET app has the thread pool. So in this case (when there's no current `SynchronizationContext` or `TaskScheduler`), the continuation is queued to the thread pool. – Stephen Cleary Oct 23 '13 at 13:54
  • @Stephen Cleary You have just answered so many questions with those 3 sentences. I had no idea about the thread pool behind a console app in .Net. With a thread pool plugged into the equation, now everything makes perfect sense. Thanks for the answer. – AxiomaticNexus Oct 23 '13 at 14:06
  • @StephenCleary: Thanks - have edited to specify the synchronization context instead of task scheduler. – Jon Skeet Oct 23 '13 at 14:34
  • @Servy: There *is* only one thread pool (at least from the public side of things). It will still be "a thread from the thread pool" but it may be a different thread. – Jon Skeet Oct 23 '13 at 14:35