Async and Await - Difference Between Console, Windows Forms and ASP.NET

Async and await - difference between console, Windows Forms and ASP.NET

Since, in UI thread based application, the UI thread is always available (unless the process is stopped explicitly or by Windows), so the ThreadPool thread responsible for executing the code after "await" in any async method, will guarantee to find the UI thread to post the results back (if any).

This is slightly confused. There's no indication that a ThreadPool thread will be required at all.

It's up to the awaitable implementation to determine where to run the continuation, but normally the current synchronization context is used to work out where to run it:

  • In a console application, there is no synchronization context, so a thread pool thread is used for the continuation.
  • In a Windows Forms application, when you're running on the UI thread the synchronization context is one which will execute code on the UI thread... so the continuation executes there (unless you use ConfigureAwait, etc...)
  • In an ASP.NET application, there's a synchronization context specific to ASP.NET itself.

See Stephen Cleary's MSDN article for more details.

It's not clear what you mean by your later question about having to call Wait or Result in ASP.NET or a console app... in both scenarios it may be required, but equally it may not be. It depends on what you're doing really. Don't forget that even a console app can start its own threads and do all kinds of other things - you can write an HTTP server in a console app, for example...

Use of async/await in console or web apps

The point with async-await is that the calling thread is always freed up when you reach the first asynchronous point (i.e. the first await of an uncompleted task).

In UI apps you have a SynchronizationContext that posts the code after the awaits to the UI thread because code that interacts with the UI must be executed by the UI thread otherwise you'll get an exception. You can control that by using ConfigureAwait(false).

In console apps (and services, etc.) there's no such need, so that code runs on some ThreadPool thread. The calling thread (which is likely to be also a ThreadPool thread) was freed up and was able to do other kinds of work in the meantime instead of blocking synchronously. So async-await improves scalability as it enables doing more work with the same amount of threads.

Utilizing Async/Await in .NET Console applications breaks when calling Application.Run() or instantiating a WinForms UserControl object

In the absence of synchronization context (or when the default SyncrhonizationContext is used), it's often possible for an await continuation to run synchronously, i.e., on the same thread where its antecedent task has ended. That can lead to obscure deadlocks, and it was one of the reasons TaskContinuationOptions.RunContinuationsAsynchronously was introduced in .NET Framework 4.6. For some more details and examples, check out this blog post: The danger of TaskCompletionSource class.

The fact that AsyncPump stops your code from hanging indicates you may have a similar situation somewhere inside mcc.Run(). As AsyncPump imposes true asynchrony for await continuations (albeit on the same thread), it reduces the chance for deadlocks.

That said, I'm not suggesting using AsyncPump or WindowsFormsSynchronizationContext as a workaround. Rather, you should try to find what exactly causes your code to hang (and where), and solve it locally, e.g. simply by wrapping the offending call with Task.Run.

One other issue I can spot in your code is that you don't wait or await the task returned by MainAsync. Because of that, at least for the console branch of your logic (especially without using AsyncPump), your program may be ending prematurely, depending on what's going in inside mcc.Run(), and you may be letting some exceptions go unobserved.

async and await in an asp.net core mvc web project

It frees up the cpu to service other requests to your controllers rather than busy wait on whatever async operations you are doing. This answer has a pretty good analogy to help explain:

Think of it like getting on a bus, there's five people waiting to get on, the first gets on, pays and sits down (the driver serviced their request), you get on (the driver is servicing your request) but you can't find your money; as you fumble in your pockets the driver gives up on you and gets the next two people on (servicing their requests), when you find your money the driver starts dealing with you again (completing your request) - the fifth person has to wait until you are done but the third and fourth people got served while you were half way through getting served. This means that the driver is the one and only thread from the pool and the passengers are the requests.


Without an async controller, the passengers behind you would have to wait ages while you looked for your money, meanwhile the bus driver would be doing no work.

Thread control flow in async .NET Console program

In short, When the SynchronizationContext.Current not is set, (which is the case on a console application). The await response is invoked on the ThreadPool.

On a Winforms/WPF a SynchronizationContext is implemented to queue the response to either the winforms controlToSendTo.BeginInvoke(); or the WPF Dispatcher.BeginInvoke();.

Reference:

  • Await, SynchronizationContext, and Console Apps (a blog post by a member of the dev team):

    But there's one common kind of application that doesn't have a SynchronizationContext: console apps. When your console application's Main method is invoked, SynchronizationContext.Current will return null. That means that if you invoke an asynchronous method in your console app, unless you do something special, your asynchronous methods will not have thread affinity: the continuations within those asynchronous methods could end up running "anywhere."

  • Parallel Computing - It's All About the SynchronizationContext (an article referenced from the official documentation for the SynchronizationContext class):

    By default, all threads in console applications and Windows Services only have the default SynchronizationContext.

    ...

    Figure 4 Summary of SynchronizationContext Implementations

    ...

    ╔═════════╦═══════════╦════════════╦════════════╦══════════╦══════════╗
    ║ ║ Specific ║ Exclusive ║ Ordered ║ Send May ║ Post May ║
    ║ ║ Thread ║ (Delegates ║ (Delegates ║ Invoke ║ Invoke ║
    ║ ║ Used to ║ Execute ║ Execute ║ Delegate ║ Delegate ║
    ║ ║ Execute ║ One at ║ in Queue ║ Directly ║ Directly ║
    ║ ║ Delegates ║ a Time) ║ Order) ║ ║ ║
    ╠═════════╬═══════════╬════════════╬════════════╬══════════╬══════════╣
    ║ ... ║ ║ ║ ║ ║ ║
    ╠═════════╬═══════════╬════════════╬════════════╬══════════╬══════════╣
    ║ Default ║ No ║ No ║ No ║ Always ║ Never ║
    ╚═════════╩═══════════╩════════════╩════════════╩══════════╩══════════╝

await Task.Run(...) behaving differently for console and windows application

That's something that happens quite often. You're causing a deadlock.

To put it simply, frameworks like WinForms and WPF "enforce" a thread synchronization context, which means that whenever you launch a new task, the rest of your code will continue on the same thread as it started. This is done to ensure that, for example, code that started on the UI thread will continue running on the same thread after the task returns.

Because you're blocking on the task (with the Wait method), and the task is trying to return a value to that blocking thread, both threads go into a deadlock state.

This behaviour doesn't happen in a console application because no thread synchronization context is enforced, the continuation of a task can run on a completely different third thread.

Stephen Cleary explains this very well here: http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

Why do .NET console programs not deadlock the way UI programs do?

There are two parts to the classic deadlock:

  1. Blocking on asynchronous code (that does not use ConfigureAwait(false) everywhere).
  2. An asynchronous context that only permits one thread in at a time.

Your example code always does (1) in both environments. The difference is in (2). Specifically, UI apps have a SynchronizationContext for their UI thread so that await will naturally return to the UI thread after the await completes. Console apps do not have that context, so the deadlock does not happen.



Related Topics



Leave a reply



Submit