4

i saw some post regarding Async and Await usage in this site. few people are saying that Async and Await complete its job on separate background thread means spawn a new background thread and few people are saying no means Async and Await does not start any separate background thread to complete its job.

so anyone just tell me what happen in case of Async and Await when it is used.

here is one small program

class Program
{
    static void Main(string[] args)
    {
        TestAsyncAwaitMethods();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }

    public async static void TestAsyncAwaitMethods()
    {
        await LongRunningMethod();
    }

    public static async Task<int> LongRunningMethod()
    {
        Console.WriteLine("Starting Long Running method...");
        await Task.Delay(5000);
        Console.WriteLine("End Long Running method...");
        return 1;
    }
}

And the output is:

Starting Long Running method...
Press any key to exit...
End Long Running method...
i3arnon
  • 113,022
  • 33
  • 324
  • 344
Mou
  • 15,673
  • 43
  • 156
  • 275
  • @Mou: You can check yourself whether its backgroud thread or UI thread by using isbackground property. – 107 Aug 21 '15 at 09:38
  • do u think it start a background thread ? – Mou Aug 21 '15 at 09:38
  • isbackground is a property of thread class....how can i use it with async and await keyword. sorry no idea. can u more elaborate. thanks – Mou Aug 21 '15 at 09:39
  • [This](http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/async-await-faq.aspx) will answer all of your questions. Go through it. In short, async-await doesn't uses any threads. It is up to the implementation of the method to use threads or not. Also [this](http://stackoverflow.com/a/25078752/2530848) – Sriram Sakthivel Aug 21 '15 at 09:50
  • Just find out by yourself, use the debugger. Use breakpoints and the Debug > Windows > Threads debugger window. You'll need it anyway sooner or later, better to get acquainted with it now. – Hans Passant Aug 21 '15 at 09:56
  • Read [these articles](https://docs.com/paulo-morgado/8860/async-await-general). – Paulo Morgado Aug 21 '15 at 11:42
  • @Mou: You may find my [There Is No Thread](http://blog.stephencleary.com/2013/11/there-is-no-thread.html) blog post useful. – Stephen Cleary Aug 22 '15 at 14:19

7 Answers7

7

The problem is that async/await is about asynchrony, not threads.

If you use Task.Run, it will indeed use a background thread (via the Thread Pool, via the Task Parallel Library).

However, for IO operations it relies on IO Completion ports to notify when the operation is complete.

The only guarantee async/await makes is that when an operation completes, it will return to your caller in the SynchronizationContext that was there when it began. In practical terms, that means it will return on the UI Thread (in a Windows application) or to a thread that can return the HTTP Response (in ASP.NET)

Richard Szalay
  • 83,269
  • 19
  • 178
  • 237
  • r u trying to say Task.Run spawn new bg thread but async/await does not start any thread.......am i right? – Mou Aug 21 '15 at 10:42
  • 1
    I'm pretty sure there are exceptions, but that's a reasonable basic assumption. – Richard Szalay Aug 21 '15 at 10:47
  • can u please list down the advantages of async/await usage. because for async programming other way out there like bgworker, task.run, threadpool and thread class. in what kind of scenario people use async/await. please answer if time permits. thanks – Mou Aug 21 '15 at 10:48
  • This is verging on a conversation, which is a stack overflow nono. bgworker and task.run both use the threadpool (which uses threads). async/await shares asynchrony with bgworker, but gives a more familiar programming experience. – Richard Szalay Aug 21 '15 at 10:53
  • u said "async/await shares asynchrony with bgworker" background worker is different class....so what is relation async/await shares with background worker class? – Mou Aug 22 '15 at 17:13
  • What I meant was, bgworker handles both the background thread _and_ returning to the original thread. – Richard Szalay Aug 22 '15 at 22:25
  • This confused me - then, If we're putting async in say, an API call, then awaiting all the way down the stack to the database call say efcore saveasync, how does it benefit the system if we're just awaiting all the way through all the layers (controller, service, data, and back up? Which async/await down the stack actually has the value-add, if any since nowhere am I starting a new thread manually, is it just building 3-4 threads and holding all of them until it unwinds?? – Dan Chase Dec 25 '22 at 20:03
6

A simple way to understand what's going on under the hood, is to use SharpLab, if you paste your short example, you'll get how the C# compiler is rewriting your code containing async / await:

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
internal class Program
{
    [CompilerGenerated]
    private sealed class <TestAsyncAwaitMethods>d__1 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncVoidMethodBuilder <>t__builder;

        private TaskAwaiter<int> <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            try
            {
                TaskAwaiter<int> awaiter;
                if (num != 0)
                {
                    awaiter = LongRunningMethod().GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        num = (<>1__state = 0);
                        <>u__1 = awaiter;
                        <TestAsyncAwaitMethods>d__1 stateMachine = this;
                        <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                        return;
                    }
                }
                else
                {
                    awaiter = <>u__1;
                    <>u__1 = default(TaskAwaiter<int>);
                    num = (<>1__state = -1);
                }
                awaiter.GetResult();
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult();
        }

        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
        }

        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }

    [CompilerGenerated]
    private sealed class <LongRunningMethod>d__2 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder<int> <>t__builder;

        private TaskAwaiter <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            int result;
            try
            {
                TaskAwaiter awaiter;
                if (num != 0)
                {
                    Console.WriteLine("Starting Long Running method...");
                    awaiter = Task.Delay(5000).GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        num = (<>1__state = 0);
                        <>u__1 = awaiter;
                        <LongRunningMethod>d__2 stateMachine = this;
                        <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                        return;
                    }
                }
                else
                {
                    awaiter = <>u__1;
                    <>u__1 = default(TaskAwaiter);
                    num = (<>1__state = -1);
                }
                awaiter.GetResult();
                Console.WriteLine("End Long Running method...");
                result = 1;
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult(result);
        }

        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
        }

        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }

    private static void Main(string[] args)
    {
        TestAsyncAwaitMethods();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }

    [AsyncStateMachine(typeof(<TestAsyncAwaitMethods>d__1))]
    [DebuggerStepThrough]
    public static void TestAsyncAwaitMethods()
    {
        <TestAsyncAwaitMethods>d__1 stateMachine = new <TestAsyncAwaitMethods>d__1();
        stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
        stateMachine.<>1__state = -1;
        AsyncVoidMethodBuilder <>t__builder = stateMachine.<>t__builder;
        <>t__builder.Start(ref stateMachine);
    }

    [AsyncStateMachine(typeof(<LongRunningMethod>d__2))]
    [DebuggerStepThrough]
    public static Task<int> LongRunningMethod()
    {
        <LongRunningMethod>d__2 stateMachine = new <LongRunningMethod>d__2();
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
        stateMachine.<>1__state = -1;
        AsyncTaskMethodBuilder<int> <>t__builder = stateMachine.<>t__builder;
        <>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }
}

As pointed in many other answers on SO (like that one), the async / await rewrite the code as a state machine just like for the yield statement with a method returning either IEnumerator, IEnumerable, IEnumerator<T>, IEnumerable<T>. Except that for async methods, you can return either:

About the last bullet you can read more about it (the fact that it's pattern based) here and there. This also involves other subtle choices that are out of the scope of your question but you can have a short explanation here about ValueTask<TResult>, IValueTaskSource<TResult>, etc.

The act of rewriting of the code is delegated to the compiler, Roslyn is basically using the AsyncRewriter class to know how to rewrite the different execution paths, branching to have an equivalent code.

In both cases where you have a valid code containing either yield or async keywords you have an initial state and depending on branching, execution path, the MoveNext() call that occurs behind the scenes will move from one state to another.

Knowing that in the case of a valid async code this kind of snippet below:

case -1:
    HelperMethods.Before();
    this.awaiter = AsyncMethods.MethodAsync(this.Arg0, this.Arg1).GetAwaiter();
    if (!this.awaiter.IsCompleted)
    {
        this.State = 0;
        this.Builder.AwaitUnsafeOnCompleted(ref this.awaiter, ref this);
    }
    break;

can roughly be translated into (see Dixin's blog for more details):

case -1: // -1 is begin.
    HelperMethods.Before(); // Code before 1st await.
    this.currentTaskToAwait = AsyncMethods.MethodAsync(this.Arg0, this.Arg1); // 1st task to await
    // When this.currentTaskToAwait is done, run this.MoveNext() and go to case 0.
    this.State = 0;
    this.currentTaskToAwait.ContinueWith(_ => that.MoveNext()); // Callback
    break;

Bear that in mind that if you have void as a return type of an async method you won't have much currentTaskToAwait =]

few people are saying that Async and Await complete its job on separate background thread means spawn a new background thread and few people are saying no means Async and Await does not start any separate background thread to complete its job.

Regarding your code, you can track which thread is (ie. id) used and whether it is from a pool or not:

public static class Program
{
    private static void DisplayCurrentThread(string prefix)
    {
        Console.WriteLine($"{prefix} - Thread Id: {Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine($"{prefix} - ThreadPool: {Thread.CurrentThread.IsThreadPoolThread}");
    }

    public static void Main(params string[] args)
    {
        DisplayCurrentThread("Main Pre");

        TestAsyncAwaitMethods();

        DisplayCurrentThread("Main Post");

        Console.ReadLine();
    }

    private static async void TestAsyncAwaitMethods()
    {
        DisplayCurrentThread("TestAsyncAwaitMethods Pre");

        await LongRunningMethod();

        DisplayCurrentThread("TestAsyncAwaitMethods Post");
    }

    private static async Task<int> LongRunningMethod()
    {
        DisplayCurrentThread("LongRunningMethod Pre");
        Console.WriteLine("Starting Long Running method...");

        await Task.Delay(500);

        Console.WriteLine("End Long Running method...");
        DisplayCurrentThread("LongRunningMethod Post");

        return 1;
    }
}

Will output for example:

Main Pre - Thread Id: 1
Main Pre - ThreadPool: False
TestAsyncAwaitMethods Pre - Thread Id: 1
TestAsyncAwaitMethods Pre - ThreadPool: False
LongRunningMethod Pre - Thread Id: 1
LongRunningMethod Pre - ThreadPool: False
Starting Long Running method...
Main Post - Thread Id: 1
Main Post - ThreadPool: False
End Long Running method...
LongRunningMethod Post - Thread Id: 4
LongRunningMethod Post - ThreadPool: True
TestAsyncAwaitMethods Post - Thread Id: 4
TestAsyncAwaitMethods Post - ThreadPool: True

You can notice that that the LongRunningMethod method terminates after the Main method, it's due to the fact that you used void as a return type for asynchronous method. An async void method should only be used for event handlers and nothing else (see Async/Await - Best Practices in Asynchronous Programming)

Also, as already mentionned by i3arnon, since no context has been passed, yes the program does (re)use a thread from the thread pool to resume its execution after the async method call.

About those "contexts", I would suggest you to read that article, the article will clarify what is a context, in particular a SynchronizationContext.

Beware that I said a threadpool thread to "resume" and not to execute the async piece of code, you can find out more about this here.

async methods are usually designed to leverage whathever latency is inherent to the underlying call, usually IO, eg. writing, reading something on a disk, querying something over the network and so forth.

The purpose of truly async methods is to avoid using threads for IO stuff which can help application to scale when you have a lot more requests. Typically can handle more requests in ASP.NET WebAPI with async resources since each of them will "free" the thread of the request whenever they will hit the database or whathever async-able calls you are making in that resource.

I suggest you to read the answers of that question

Void-returning async methods have a specific purpose: to make asynchronous event handlers possible. It is possible to have an event handler that returns some actual type, but that doesn't work well with the language; invoking an event handler that returns a type is very awkward, and the notion of an event handler actually returning something doesn't make much sense.

Event handlers naturally return void, so async methods return void so that you can have an asynchronous event handler. However, some semantics of an async void method are subtly different than the semantics of an async Task or async Task method.

A way to avoid this is to leverage a C# 7.1 feature and expect a Task as a return type instead of the void:

public static class Program
{
    private static void DisplayCurrentThread(string prefix)
    {
        Console.WriteLine($"{prefix} - Thread Id: {Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine($"{prefix} - ThreadPool: {Thread.CurrentThread.IsThreadPoolThread}");
    }

    public static async Task Main(params string[] args)
    {
        DisplayCurrentThread("Main Pre");

        await TestAsyncAwaitMethods();

        DisplayCurrentThread("Main Post");

        Console.ReadLine();
    }

    private static async Task TestAsyncAwaitMethods()
    {
        DisplayCurrentThread("TestAsyncAwaitMethods Pre");

        await LongRunningMethod();

        DisplayCurrentThread("TestAsyncAwaitMethods Post");
    }

    private static async Task<int> LongRunningMethod()
    {
        DisplayCurrentThread("LongRunningMethod Pre");
        Console.WriteLine("Starting Long Running method...");

        await Task.Delay(500);

        Console.WriteLine("End Long Running method...");
        DisplayCurrentThread("LongRunningMethod Post");

        return 1;
    }
}

You'll then get

Main Pre - Thread Id: 1
Main Pre - ThreadPool: False
TestAsyncAwaitMethods Pre - Thread Id: 1
TestAsyncAwaitMethods Pre - ThreadPool: False
LongRunningMethod Pre - Thread Id: 1
LongRunningMethod Pre - ThreadPool: False
Starting Long Running method...
End Long Running method...
LongRunningMethod Post - Thread Id: 4
LongRunningMethod Post - ThreadPool: True
TestAsyncAwaitMethods Post - Thread Id: 4
TestAsyncAwaitMethods Post - ThreadPool: True
Main Post - Thread Id: 4
Main Post - ThreadPool: True

Which looks more like what you would normally expect.

More resources about async / await:

Natalie Perret
  • 8,013
  • 12
  • 66
  • 129
3

Both of the your statements are probably true, but are confusing.

Async-await does usually complete on a separate background thread but it doesn't mean it starts any separate background thread to complete the job.

The point of these asynchronous operations is to to not hold a thread while an asynchronous operation is being executed because true asynchronous operations do not require a thread.

The parts before that operation can be CPU bound and do require a thread and they are executed by the calling thread. The parts after that operation (which is usually called the completion) also require a thread. If there's a SynchronizationContext (like there is in UI or asp.net apps) or TaskScheduler then that part is handled by them. If there isn't any that part is scheduled on the ThreadPool to be executed by an already existing background thread.

So, in your example Task.Delay creates a Task that completes after 5 seconds. During that delay there's no need for a thread so you can use async-await.

The flow of your example is this: The main thread starts executing Main, calls TestAsyncAwaitMethods, calls LongRunningMethod, prints the first message, calls Task.Delay, registers the rest of the method as a continuation to execute after the Task.Delay completes, return to Main, print the message and waits synchronously (blocks) on Console.ReadLine.

After 5 seconds the timer in Task.Delay ends and completes the Task returned from Task.Delay. The continuation is then scheduled on the ThreadPool (since it's a console app) and a ThreadPool thread that was assigned that task prints "End Long Running method...".

In conclusion, a true asynchronous operation doesn't need a thread to be able to run, but it does need a thread after it has completed which is usually a background thread from the ThreadPool but not necessarily.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • thanks for reply. u said "Async-await does usually complete on a separate background thread but it doesn't mean it starts any separate background thread to complete the job" here u said Async-await complete its job on separate thread but it does not create or start any thread then who and how bg thread comes into scene on which Async-await work or complete its job. bit confusing. – Mou Aug 22 '15 at 17:20
  • @Mou the `ThreadPool`. Unless there's a `SynchronizationContext` or `TaskScheduler` that acts otherwise when an asynchronous operation completes its continuation is scheduled on the `ThreadPool`. So a background thread is burrowed and returned, but not for the actual operation. – i3arnon Aug 22 '15 at 17:24
  • Sorry not clear what u try to say. can u plzz elaborate. – Mou Aug 22 '15 at 17:32
  • @Mou A truly asynchronous operation, like the delay in `Task.Delay` doesn't a need a thread to run on but if you have an async method with `await Task.Delay(1000);` with code after that then this code needs some thread to continue running on. Unless specified otherwise that thread will be a `ThreadPool` thread. That's why it's correct to say that async-await doesn't create background thread and say that async-await completes on a background thread. – i3arnon Aug 22 '15 at 17:36
1

You are asking the wrong question

In effect you are asking, how does a parcel get to my doorstep? By ship or by plane?

The point is that your door step doesn't care wither the parcel was delivered by sea or air.

However the main reason for Microsoft to develop the Task/async/await framework was to take advantage of Event based programming as opposed to Thread based programming.

In general Event based programming is MUCH more efficient and faster than Thread based programming. Which is why most of the .net API uses it. Up until now, however most people avoided Event based programming because it is extremely difficult to understand (again, async/wait was put into place to make this simple).

Aron
  • 15,464
  • 3
  • 31
  • 64
  • why u think async/wait is related to event based programming? what event is associated with async/wait? bgWorker is event based programming that i know. explain more to prove that async/wait is event based programming. – Mou Aug 21 '15 at 10:45
  • @Mou I don't mean EAP pattern programming. I mean that asynchrony is achieved through callbacks and interrupts. – Aron Aug 21 '15 at 11:19
  • can u plzz come with sample code for callbacks and interrupts. – Mou Aug 21 '15 at 13:45
0

Calling await is only possible inside methods marked as async. Once you await a function, the framework knows how to remember your current calling environment and return control to it once the awaited function completes.

You can only ever await functions that return Tasks. So all await deals with is the Task object that gets returned (and until a task is returned, the method you are awaiting is executing synchronously)

To provide you with a Task, the method you are awaiting could spawn a new thread to do it's job, it could synchronously return a completed task with a value (creating a task from a result), it can do whatever it wants. All await does is give the control back to the parent of your function until and unless the Task object you received from the awaitable method is complete. At that point it will continue the execution of your method from the await line.

irreal
  • 2,217
  • 18
  • 23
0

Need understand two things: a) async/await use tasks(tasks use thread pool) b) async/await is NOT for parallel work.

Just compile this and look at Id's:

static void Main(string[] args)
    {
        Console.WriteLine("Id main thread is: {0}", Thread.CurrentThread.ManagedThreadId);
        TestAsyncAwaitMethods();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }

    public async static void TestAsyncAwaitMethods()
    {
        Console.WriteLine("Id thread (void - 0) is: {0}", Thread.CurrentThread.ManagedThreadId);
        var _value = await LongRunningMethod();
        Console.WriteLine("Id thread (void - 1) is: {0}", Thread.CurrentThread.ManagedThreadId);
    }

    public static async Task<int> LongRunningMethod()
    {
        Console.WriteLine("Id thread (int) is: {0}", Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine("Starting Long Running method...");
        await Task.Delay(1000);
        Console.WriteLine("End Long Running method...");
        return 1;
    }
0

The simplest solution is,

await LongRunningMethod().wait();

It will cause the main thread to wait (non blocking) till the LongRunningMethod finishes execution.

XPD
  • 1,121
  • 1
  • 13
  • 26