Does the use of async/await create a new thread?

50,178

Solution 1

In short NO

From Asynchronous Programming with Async and Await : Threads

The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available.

Solution 2

According to MSDN : async keyword

An async method runs synchronously until it reaches its first await expression, at which point the method is suspended until the awaited task is complete. In the meantime, control returns to the caller of the method, as the example in the next section shows.

Here is a sample code to check it :

class Program

{
    static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();
    }

    private void Print(string txt)
    {
        string dateStr = DateTime.Now.ToString("HH:mm:ss.fff");
        Console.WriteLine($"{dateStr} Thread #{Thread.CurrentThread.ManagedThreadId}\t{txt}");
    }

    private void Run()
    {
        Print("Program Start");
        Experiment().Wait();
        Print("Program End. Press any key to quit");
        Console.Read();
    }

    private async Task Experiment()
    {
        Print("Experiment code is synchronous before await");
        await Task.Delay(500);
        Print("Experiment code is asynchronous after first await");
    }
}

And the result : Experiment result: the code after the await executes in another Thread

We see the code of Experiment() method after await executes on another Thread.

But if I replace the Task.Delay by my own code (method SomethingElse) :

   class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();
    }

    private void Print(string txt)
    {
        string dateStr = DateTime.Now.ToString("HH:mm:ss.fff");
        Console.WriteLine($"{dateStr} Thread #{Thread.CurrentThread.ManagedThreadId}\t{txt}");
    }

    private void Run()
    {
        Print("Program Start");
        Experiment().Wait();
        Print("Program End. Press any key to quit");
        Console.Read();
    }

    private async Task Experiment()
    {
        Print("Experiment code is synchronous before await");
        await SomethingElse();
        Print("Experiment code is asynchronous after first await");
    }

    private Task SomethingElse()
    {
        Print("Experiment code is asynchronous after first await");
        Thread.Sleep(500);
        return (Task.CompletedTask);
    }
}

I notice the thread remains the same !

The thread is the same even with async/await

In conclusion, I'll say async/await code could use another thread, but only if the thread is created by another code, not by async/await.

In this case, I think Task.Delay created the thread, so I can conclude async/await does not create a new Thread like said by @Adriaan Stander.

Solution 3

Sorry for being late to the party.

I am new to TPL and I am wondering: How does the asynchronous programming support that is new to C# 5.0 (via the new async and await keywords) relate to the creation of threads?

async/await is not introduced for thread creation, but to utilize the current thread optimally.

Your app might read files, wait for response from another server or even do a computation with high memory access (Simply any IO task). These tasks are not CPU intensive (Any task that will not use 100% of your thread).

Think about the case when you are processing 1000 non CPU intensive tasks. In this case, process of creating 1000s of OS level thread might eat up more CPU and Memory than doing actual work on a single thread (4mb per thread in Windows, 4MB * 1000 = 4GB). At the same time if you run all the tasks sequentially, you might have to wait until the IO tasks gets finished. Which end up in long time to complete the task, while keeping the CPU idle.

Since we require parallelism to complete multiple tasks quickly, at the same time all parallel tasks are not CPU hungry, but creating threads is inefficient.

The compiler will break the execution at any method call to an async method (which gets called with an await) and immediately execute the code outside of the current code branch, once an await is reached, the execution will go inside the previous async. This will be repeated again and again until all the async calls are completed and their awaiters are satisfied.

If any of the async method have heavy CPU load without a call to an async method, then yes, your system will become unresponsive and all the remaining async methods will not get called until the current task is finished.

Solution 4

So I've been reading up on the threading model, and Async / Await can certainly lead to new threads being used (not necessarily created - the pool creates them at application start). It's up to the scheduler to determine if a new thread is needed. And as I see it, a call to an awaitable function may have internal details that increase the chances of the scheduler utilizing another thread; simply because more work means more opportunities / reasons for the scheduler to divvy out work.

WinRT async operations automatically happen on the thread pool. And typically you will be calling FROM the thread pool, except for UI thread work .. Xaml/Input/Events.

Async operations started on Xaml/UI threads have their results delivered back to the [calling] UI thread. But asynchronous operation results started from a thread pool thread are delivered wherever the completion happens, which may not be the same thread you were on before. The reason behind this is that code written for the thread pool is likely to be written to be thread safe and it is also for efficiency, Windows doesn't have to negotiate that thread switch.

So again, in answer to the OP, new threads are not necessarily created but your application can and will use multiple threads to complete asynchronous work.

I know this seems to contradict some of the literature regarding async / await, but that's because although the async / await construct is not by itself multithreaded. Awaitables are the, or one of the mechanisms by which the scheduler can divide work and construct calls across threads.

This is at the limit of my knowledge right now regarding async and threading, so I might not have it exactly right, but I do think it's important to see the relationship between awaitables and threading.

Share:
50,178
dev hedgehog
Author by

dev hedgehog

Updated on July 20, 2022

Comments

  • dev hedgehog
    dev hedgehog over 1 year

    I am new to TPL and I am wondering: How does the asynchronous programming support that is new to C# 5.0 (via the new async and await keywords) relate to the creation of threads?

    Specifically, does the use of async/await create a new thread each time that they are used? And if there many nested methods that use async/await, is a new thread created for each of those methods?

  • dudeNumber4
    dudeNumber4 over 7 years
    No matter how many times I look at this issue, I still don't understand. "Async methods don't require multithreading because an async method doesn't run on its own thread" Ergo -> another thread. How can it be otherwise?
  • Vakhtang
    Vakhtang over 7 years
    Not every operation needs a thread. There are a lot of processors/controllers on a typical system including disk controllers, network card controllers, GPUs etc. They just need to receive a command from a processor. They then proceed to execute the command and tell the processor when finished (notify it by interrupting it or some other mechanism). Until then, there is no thread involved. The issuing thread can either go to sleep or go to a thread pool where it can be reused. When the command is finished, program execution may be continued by the sleeping thread or a thread pool thread.
  • Admin
    Admin over 7 years
    what if async/await method is a cpu bound method Task.Run is used to process long running process in that case asycn with require the new thread, no?
  • Bart
    Bart about 7 years
    @dudeNumber4 "An async method doesn't run on it's own thread -> ergo another thread". No it runs on the same thread! The same thread as the method that called it. It just returns to that calling method in case it starts 'await'ing something so as not to waste CPU cycles.
  • ajeh
    ajeh about 6 years
    @Bart CPU cycles are irrelevant in this q/a.
  • Enigmativity
    Enigmativity over 5 years
    Why does the code sample create a new thread? What is specific about this code that makes it a good example to show?
  • Gavin Williams
    Gavin Williams over 5 years
    GetFileAsync() creates threads and will return on the created thread, why it creates threads? Because that's the way Microsoft designed it I guess. So if you think you can use async/await as in this example and still be on the thread you were on when you called GetFileAsync() you would be wrong, and in thread sensitive situations such as DirectX device creation / rendering you're application will crash.
  • Scott Chamberlain
    Scott Chamberlain over 5 years
    @GavinWilliams the thread that is used for the callback is chosen by TaskScheduler.Current at the time of calling await not the function you called await on. The fact that it runs on a different thread when it returns has nothing to do with GetFileAsync
  • Enigmativity
    Enigmativity over 5 years
    @GavinWilliams - There is nothing in the code that you've shown that makes this a good example without the reader having specific knowledge of the implementation of GetFileAsync. That makes this a poor example.
  • Gavin Williams
    Gavin Williams over 5 years
    @Enigmativity I don't get you sorry. I think typically a user of GetFileAsync or of any other method for that matter is not going to have specific knowledge of it's implementation. I gave it as an example of async / await usage resulting in threads being created. It does show that result. It's a very common async method and I would suggest is a typical example. Is there something atypical about it that makes it a poor example? In practice if you use async/await you have to be careful to manage which threads your code is on.
  • Voo
    Voo over 5 years
    I'd be very surprised if something such as GetFileAsync always created new threads. Any proof for that claim? Particularly IO rarely had to create threads since completion ports are a thing. But then I don't know much about uwp and what the function does in detail
  • Gavin Williams
    Gavin Williams over 5 years
    Only empirical proof.
  • Gavin Williams
    Gavin Williams over 5 years
    I always have to be careful when using async because it can throw sections of my asset creation code onto a different from my rendering thread and you can't use the DirectX Immediate context from different threads.
  • Enigmativity
    Enigmativity over 5 years
    @GavinWilliams - If I gave you the signature of a method, lets say Task<int> GetAgeAsync(), you can't tell by looking at it if this will or will not create a thread. You have to show the implementation. So your example is the same. There is no way to tell if this creates a thread or not. Had you presented your own example with the full implementation of GetFileAsync or GetAgeAsync then you could show that it does or does not.
  • Gavin Williams
    Gavin Williams over 5 years
    There is no way to tell - that's what I'm saying. You don't know if a thread will be created or not, in my answer I say .. "Using Async/Await doesn't necessarily cause a new thread to be created. But the use of Async/Await can lead to a new thread to be created because the awaitable function may internally spawn a new thread." That is true, although it seems that there is another mechanism by which a thread can be created, as suggested by Scott Chamberlain, and that is that the TaskSchedular can make it's own determination as to whether to spawn a thread.
  • Voo
    Voo over 5 years
    @Gavin I don't see how GetFileAsync(path)); // << new threads and the following code spawns new threads is supposed to mean "I don't know whether this creates new threads or not".
  • maoyang
    maoyang about 5 years
    I think it uses a similar mode with Nodejs. There are a thread pool and an event loop behind the idea.
  • Welcor
    Welcor almost 4 years
    I think this is wrong. you mix up pure await DoAsync() with await Task.Run(Do). the latter will use the thread pool because of the Task.Run but not because of the await. I mean even the official documentation says The async and await keywords don't cause additional threads to be created..
  • Gavin Williams
    Gavin Williams almost 4 years
    @Blechdose Yeah, so as I understand it, if you're just using the async / await glue on a Task with synchronous code (single threaded), then that's fine, it will run on the calling thread. But for any awaitable methods that are black boxes. You don't know if another thread will be used. You should never make assumptions about which thread you're on after awaiting.
  • Elo
    Elo over 3 years
    Why do you say "The compiler will break the execution at any method call to an async method (regardless it is awaited or not)" ? Microsoft says : "If the method that the async keyword modifies doesn't contain an await expression or statement, the method executes synchronously". In this case, the async keyword doesn't break execution flow at all. Source : docs.microsoft.com/fr-fr/dotnet/csharp/language-reference/…
  • The Muffin Man
    The Muffin Man over 3 years
    The name async implies IO bound work such as if the work is to send an HTTP request to amazon you don't need to spawn a thread that does nothing, but wait. However, the async/await feature also allows you a cleaner interface for CPU bound work (that involves threads) that would normally involve messy callback syntax. As far as I understand, the final piece is ConfigureAwait(false) which specifies whether the work needs to absolutely be synced back to the thread that started it (which I believe in most cases is no) which isn't as performant.
  • VibeeshanRC
    VibeeshanRC about 3 years
    The wording "(regardless it is awaited or not)" is wrong. fixed now. thanks for the correction.
  • Bartosz
    Bartosz about 3 years
    One thing that puzzles me is that here docs.microsoft.com/en-us/dotnet/api/… it is said work performed by a Task object typically executes asynchronously on a thread pool thread rather than synchronously on the main application thread, which to me sort of means that there is a thread assigned. Although I read Stephen Cleary's blog post and am convinced there is no thread... :) blog.stephencleary.com/2013/11/there-is-no-thread.htm
  • Timeless
    Timeless over 2 years
    this explanation is pretty good. stackoverflow.com/a/59918139/926460
  • Denny Jacob
    Denny Jacob over 2 years
    A thread has gaps in its processing timeline when it is waiting on devices like GPU, disk controller etc. Awaited tasks use these gaps in a non blocking way. The gaps are so many that your task appears to have completed concurrently.
  • DaniDev
    DaniDev over 2 years
    Thanks for this. I think it clarifies some of the confusion which arises from the fact that Async Await is often used with a Task in TAP (Task Async Pattern) . As such some people mistakenly assume that the Async Await create a new thread rather then Task. As I understand it the TAP pattern provides a way to manage multi threading functionality more cleanly.