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

5

102

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?

Pincenez answered 3/12, 2014 at 7:18 Comment(4)
Depends on how the method your calling using await is implemented. You should read blog.stephencleary.com/2013/11/there-is-no-thread.html But be warned. We shall dive deep.Piroshki
No, for example see https://mcmap.net/q/82946/-why-does-quot-await-loadasync-quot-freeze-the-ui-while-quot-await-task-run-gt-load-quot-does-notBeaudoin
Related/duplicate: async - stay on the current thread?Guru
Related/duplicate: If async-await doesn't create any additional threads, then how does it make applications responsive?Guru
R
83

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.

Reasonable answered 3/12, 2014 at 7:22 Comment(10)
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?Hine
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.Muddle
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?Skid
@Hine "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.Fundamentalism
@Fundamentalism CPU cycles are irrelevant in this q/a.Structuralism
I think it uses a similar mode with Nodejs. There are a thread pool and an event loop behind the idea.Scauper
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.Sarnen
One thing that puzzles me is that here learn.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.htmBaptiste
this explanation is pretty good. https://mcmap.net/q/82949/-does-quot-async-quot-run-in-a-separate-thread-duplicateSee
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.Caruthers
R
42

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.

Risk answered 14/1, 2020 at 11:1 Comment(1)
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.Quinone
R
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.

Rhinitis answered 16/10, 2019 at 12:15 Comment(4)
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 : learn.microsoft.com/fr-fr/dotnet/csharp/language-reference/…Risk
The wording "(regardless it is awaited or not)" is wrong. fixed now. thanks for the correction.Rhinitis
@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?Handedness
@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.Rhinitis
F
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.

Finegan answered 17/8, 2018 at 8:52 Comment(2)
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..Charmion
@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.Finegan
F
-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());
    ...
}
Finegan answered 16/8, 2018 at 3:40 Comment(11)
Why does the code sample create a new thread? What is specific about this code that makes it a good example to show?Stannum
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.Finegan
@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 GetFileAsyncDowling
@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.Stannum
@Stannum 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.Finegan
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 detailOvenbird
Only empirical proof.Finegan
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.Finegan
@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.Stannum
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.Finegan
@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".Ovenbird

© 2022 - 2024 — McMap. All rights reserved.