简体   繁体   English

使用 async/await 会创建一个新线程吗?

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

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?我是TPL的新手,我想知道:C# 5.0 新增的异步编程支持(通过新的asyncawait关键字)与线程的创建有何关系?

Specifically, does the use of async/await create a new thread each time that they are used?具体来说,每次使用async/await是否都会创建一个新线程? And if there many nested methods that use async/await , is a new thread created for each of those methods?如果有许多使用async/await的嵌套方法,是否会为这些方法中的每一个创建一个新线程?

In short NO总之没有

From Asynchronous Programming with Async and Await : Threads来自Async 和 Await 的异步编程:线程

The async and await keywords don't cause additional threads to be created. async 和 await 关键字不会导致创建额外的线程。 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.您可以使用 Task.Run 将受 CPU 限制的工作移至后台线程,但后台线程对仅等待结果可用的进程没有帮助。

According to MSDN : async keyword根据 MSDN : async 关键字

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.异步方法同步运行,直到它到达它的第一个 await 表达式,此时该方法将挂起,直到等待的任务完成。 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 :结果: 实验结果:await后的代码在另一个线程中执行

We see the code of Experiment() method after await executes on another Thread .我们在另一个 Thread 上执行 await 后看到 Experiment() 方法的代码。

But if I replace the Task.Delay by my own code (method SomethingElse) :但是如果我用我自己的代码(方法SomethingElse)替换Task.Delay:

   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 !我注意到线程保持不变!

即使使用 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.总之,我会说 async/await 代码可以使用另一个线程,但前提是该线程是由另一个代码创建的,而不是由 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.在这种情况下,我认为Task.Delay创建了线程,所以我可以得出结论 async/await 不会像@Adriaan Stander 所说的那样创建新线程。

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?我是 TPL 的新手,我想知道:C# 5.0 的新异步编程支持(通过新的 async 和 await 关键字)与线程的创建有何关系?

async/await is not introduced for thread creation, but to utilize the current thread optimally. async/await不是为了线程创建而引入的,而是为了最佳地利用当前线程。

Your app might read files, wait for response from another server or even do a computation with high memory access (Simply any IO task).您的应用程序可能会读取文件、等待来自另一台服务器的响应,甚至执行具有高内存访问权限的计算(任何 IO 任务)。 These tasks are not CPU intensive (Any task that will not use 100% of your thread).这些任务不是 CPU 密集型的(任何不会使用 100% 线程的任务)。

Think about the case when you are processing 1000 non CPU intensive tasks .考虑一下您正在处理 1000 个非 CPU 密集型任务的情况 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 ).在这种情况下,创建 1000 个操作系统级线程的过程可能比在单个线程上执行实际工作消耗更多的 CPU 和内存(Windows 中每个线程 4mb,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.同时,如果您按顺序运行所有任务,则可能必须等到 IO 任务完成。 Which end up in long time to complete the task, while keeping the CPU idle.这最终会在很长的时间内完成任务,同时保持 CPU 空闲。

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.由于我们需要并行性来快速完成多个任务,同时所有并行任务都不会占用 CPU,但创建线程效率低下。

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 .编译器将在对async方法(通过 await 调用)的任何方法调用时中断执行,并立即执行当前代码分支之外的代码,一旦达到await ,执行将进入前一个async This will be repeated again and again until all the async calls are completed and their awaiters are satisfied.这将一次又一次地重复,直到所有异步调用都完成并且它们的awaiters得到满足awaiters

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.如果任何异步方法在没有调用异步方法的情况下具有沉重的 CPU 负载,那么是的,您的系统将变得无响应,并且在当前任务完成之前不会调用所有剩余的异步方法。

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).所以我一直在阅读线程模型,并且 Async / Await 肯定会导致使用新线程(不一定创建 - 池在应用程序启动时创建它们)。 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. WinRT 异步操作自动发生在线程池上。 And typically you will be calling FROM the thread pool, except for UI thread work .. Xaml/Input/Events.通常你会从线程池调用,除了 UI 线程工作 .. Xaml/Input/Events。

Async operations started on Xaml/UI threads have their results delivered back to the [calling] UI thread.在 Xaml/UI 线程上启动的异步操作将其结果传递回 [调用] UI 线程。 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.这背后的原因是为线程池编写的代码很可能被编写为线程安全的并且也是为了效率,Windows 不必协商该线程切换。

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.因此,作为对 OP 的回应,不一定会创建新线程,但您的应用程序可以并且将使用多个线程来完成异步工作。

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.我知道这似乎与一些关于 async/await 的文献相矛盾,但那是因为尽管 async/await 构造本身不是多线程的。 Awaitables are the, or one of the mechanisms by which the scheduler can divide work and construct calls across threads. Awaitable 是调度程序可以跨线程划分工作和构造调用的机制或机制之一。

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.这是我目前关于异步和线程的知识的极限,所以我可能不完全正确,但我确实认为了解 awaitables 和线程之间的关系很重要。

What really helped me get my head around this.是什么真正帮助我解决了这个问题。

Think of await keyword as a return from your methodawait关键字视为方法的return

And everything after it - is a "callback".之后的一切 - 都是“回调”。

That "callback" is executed later.该“回调”稍后执行。 Probably even on the same thread in most cases.在大多数情况下,甚至可能在同一线程上。 Can be a different thread in some cases, but it's still ONE THREAD .在某些情况下可以是不同的线程,但它仍然是ONE THREAD

PS If your async method has many nested async methods deeper down the chain - well, it's the same. PS 如果您的async方法在链的更深处有许多嵌套的async方法 - 好吧,它是一样的。 It's just many "callers" and "callbacks" all the way down.一路下来只是很多“来电者”和“回调”。 But they all execute on ONE thread.但它们都在一个线程上执行。 And somewhere deep inside those nested calls, at the end of the chain, there's a truly async-API (i/o writing to disk, or a delay, or a network request or something else "truly" async in nature) that will do the async work via i/o drivers and system interrupts, and call your callbacks once finished.在这些嵌套调用的深处,在链的末端,有一个真正的异步 API(I/O 写入磁盘,或延迟,或网络请求或其他本质上“真正”异步的东西)可以做到异步通过 I/O 驱动程序和系统中断工作,并在完成后调用您的回调。

PPS If the async method you're calling explicitly calls Task.Run or Task.Yield then it's a different story. PPS 如果您正在调用的异步方法显式调用Task.RunTask.Yield那么情况就不同了。

Using Async/Await doesn't necessarily cause a new thread to be created.使用 Async/Await 不一定会导致创建新线程。 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.但是使用 Async/Await 可能会导致创建一个新线程,因为 awaitable 函数可能会在内部生成一个新线程。 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());
    ...
}

In case of Java Spring Framework, a method annotated with @Async runs in a separate thread.在 Java Spring Framework 的情况下,使用@Async注释的方法在单独的线程中运行。 Quoting from official guide ( https://spring.io/guides/gs/async-method ) -引用官方指南( 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. findUser 方法使用 Spring 的 @Async 注释进行标记,表明它应该在单独的线程上运行。 The method's return type is CompletableFuture instead of User, a requirement for any asynchronous service.该方法的返回类型是 CompletableFuture 而不是 User,这是任何异步服务的要求。

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).当然,在后端它使用线程池和队列(异步任务等待线程返回池中)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM