98

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?

Community
  • 1
  • 1
dev hedgehog
  • 8,698
  • 3
  • 28
  • 55
  • 10
    Depends on how the method your calling using `await` is implemented. You should read http://blog.stephencleary.com/2013/11/there-is-no-thread.html But be warned. *We shall dive deep.* – MarcinJuraszek Dec 03 '14 at 07:19
  • 1
    No, for example see http://stackoverflow.com/a/27071434/876814 – Rico Suter Dec 03 '14 at 09:53
  • Related/duplicate: [async - stay on the current thread?](https://stackoverflow.com/q/17661428/11683) – GSerg May 15 '19 at 08:28
  • Related/duplicate: [If async-await doesn't create any additional threads, then how does it make applications responsive?](https://stackoverflow.com/q/37419572/11683) – GSerg May 15 '19 at 08:42

6 Answers6

82

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.

Welcor
  • 2,431
  • 21
  • 32
Adriaan Stander
  • 162,879
  • 31
  • 289
  • 284
  • 37
    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? – dudeNumber4 Oct 18 '16 at 15:11
  • 27
    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. – Vakhtang Dec 15 '16 at 20:32
  • 1
    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? –  Dec 25 '16 at 14:50
  • 12
    @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. – Bart Feb 15 '17 at 22:24
  • 1
    @Bart CPU cycles are irrelevant in this q/a. – ajeh Apr 18 '18 at 20:39
  • I think it uses a similar mode with Nodejs. There are a thread pool and an event loop behind the idea. – maoyang Mar 17 '19 at 23:09
  • 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. – The Muffin Man Dec 22 '20 at 01:34
  • One thing that puzzles me is that here https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-5.0 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... :) http://blog.stephencleary.com/2013/11/there-is-no-thread.htm – Bartosz Mar 28 '21 at 11:58
  • this explanation is pretty good. https://stackoverflow.com/a/59918139/926460 – Timeless Sep 09 '21 at 02:43
  • 1
    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. – Denny Jacob Oct 14 '21 at 14:56
40

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.

Elo
  • 2,234
  • 22
  • 28
  • 4
    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. – DaniDev Dec 26 '21 at 23:07
11

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.

Vibeeshan Mahadeva
  • 7,147
  • 8
  • 52
  • 102
  • 1
    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 : https://learn.microsoft.com/fr-fr/dotnet/csharp/language-reference/keywords/async – Elo Oct 07 '20 at 07:51
  • The wording "(regardless it is awaited or not)" is wrong. fixed now. thanks for the correction. – Vibeeshan Mahadeva Mar 26 '21 at 04:54
  • @VibeeshanRC I don't understand the re-orchestrating process. If you async a database call, I understand that you free the thread until the database returns. But surely SOMETHING must be "staring down the pipe" waiting for the database call to return and then do the job of remarshalling the synchronization context before a worker thread can pick it up. What is this SOMETHING if it is not a(nother) worker thread? – Tormod Dec 14 '22 at 08:20
  • @Tormod, ultimately all these methods use a specified list of IO APIs of the OS. If it's a DB connection, that last part is a TCP connection. where here we use Microsoft Provided async awaited methods which do their magic. Ultimately they have implemented this code on file operations and network operations. The rest of all scenarios (Like DB connection, social media apis, and you favourite APIs ) just uses the two underlying operations. – Vibeeshan Mahadeva Jan 18 '23 at 14:02
5

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.

Gavin Williams
  • 446
  • 1
  • 4
  • 14
  • 3
    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.`. – Welcor May 15 '20 at 13:48
  • 2
    @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. – Gavin Williams May 23 '20 at 16:43
-3

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. And it often does, making the statement 'No, it doesn't spawn threads' almost useless in practice. For example, the following code spawns new threads.

VisualProcessor.Ctor()
{
    ...
    BuildAsync();
}

async void BuildAsync()
{
    ...
    TextureArray dudeTextures = await TextureArray.FromFilesAsync(…);
}

public static async Task<TextureArray> FromFilesAsync(...)
{    
    Debug.WriteLine("TextureArray.FromFilesAsync() T1 : Thread Id = " + GetCurrentThreadId());
    List<StorageFile> files = new List<StorageFile>();
    foreach (string path in paths)
    {
        if (path != null)
            files.Add(await Package.Current.InstalledLocation.GetFileAsync(path)); // << new threads
        else
            files.Add(null);
    }
    Debug.WriteLine("TextureArray.FromFilesAsync() T2 : Thread Id = " + GetCurrentThreadId());
    ...
}
Gavin Williams
  • 446
  • 1
  • 4
  • 14
  • Why does the code sample create a new thread? What is specific about this code that makes it a good example to show? – Enigmativity Aug 16 '18 at 04:20
  • 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. – Gavin Williams Aug 16 '18 at 04:33
  • 1
    @GavinWilliams the thread that is used for the callback is chosen by [`TaskScheduler.Current`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskscheduler.current?redirectedfrom=MSDN&view=netframework-4.7.2#System_Threading_Tasks_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` – Scott Chamberlain Aug 16 '18 at 05:06
  • 1
    @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. – Enigmativity Aug 16 '18 at 05:33
  • 1
    @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. – Gavin Williams Aug 16 '18 at 06:34
  • 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 – Voo Aug 16 '18 at 06:35
  • Only empirical proof. – Gavin Williams Aug 16 '18 at 06:36
  • 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. – Gavin Williams Aug 16 '18 at 06:44
  • 1
    @GavinWilliams - If I gave you the signature of a method, lets say `Task 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. – Enigmativity Aug 16 '18 at 06:53
  • 2
    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. – Gavin Williams Aug 16 '18 at 06:57
  • 1
    @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". – Voo Aug 16 '18 at 08:19
-3

In case of Java Spring Framework, a method annotated with @Async runs in a separate thread. Quoting from official guide (https://spring.io/guides/gs/async-method) -

The findUser method is flagged with Spring’s @Async annotation, indicating that it should run on a separate thread. The method’s return type is CompletableFuture instead of User, a requirement for any asynchronous service.

Of course in the backend it uses a Thread Pool and a Queue (where async tasks wait for a thread to be back in the pool).

Alok Mishra
  • 926
  • 13
  • 39
  • 1
    This is a .NET question. It has nothing to do with Java. The @Async annotation is nothing like the `async/await` feature in Rust, Javascript, Typescript and C#. Windows IO is always asynchronous so async IO methods could work without using a Threadpool thread. – Panagiotis Kanavos Jul 20 '22 at 11:55