繁体   English   中英

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

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

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

具体来说,每次使用async/await是否都会创建一个新线程? 如果有许多使用async/await的嵌套方法,是否会为这些方法中的每一个创建一个新线程?

总之没有

来自Async 和 Await 的异步编程:线程

async 和 await 关键字不会导致创建额外的线程。 异步方法不需要多线程,因为异步方法不在自己的线程上运行。 该方法在当前同步上下文上运行,并且仅当该方法处于活动状态时才在线程上使用时间。 您可以使用 Task.Run 将受 CPU 限制的工作移至后台线程,但后台线程对仅等待结果可用的进程没有帮助。

根据 MSDN : async 关键字

异步方法同步运行,直到它到达它的第一个 await 表达式,此时该方法将挂起,直到等待的任务完成。 同时,控制权返回给方法的调用者,如下一节中的示例所示。

这是检查它的示例代码:

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");
    }
}

结果: 实验结果:await后的代码在另一个线程中执行

我们在另一个 Thread 上执行 await 后看到 Experiment() 方法的代码。

但是如果我用我自己的代码(方法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);
    }
}

我注意到线程保持不变!

即使使用 async/await 线程也是一样的

总之,我会说 async/await 代码可以使用另一个线程,但前提是该线程是由另一个代码创建的,而不是由 async/await 创建的。

在这种情况下,我认为Task.Delay创建了线程,所以我可以得出结论 async/await 不会像@Adriaan Stander 所说的那样创建新线程。

很抱歉在聚会上迟到了。

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

async/await不是为了线程创建而引入的,而是为了最佳地利用当前线程。

您的应用程序可能会读取文件、等待来自另一台服务器的响应,甚至执行具有高内存访问权限的计算(任何 IO 任务)。 这些任务不是 CPU 密集型的(任何不会使用 100% 线程的任务)。

考虑一下您正在处理 1000 个非 CPU 密集型任务的情况 在这种情况下,创建 1000 个操作系统级线程的过程可能比在单个线程上执行实际工作消耗更多的 CPU 和内存(Windows 中每个线程 4mb,4MB * 1000 = 4GB )。 同时,如果您按顺序运行所有任务,则可能必须等到 IO 任务完成。 这最终会在很长的时间内完成任务,同时保持 CPU 空闲。

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

编译器将在对async方法(通过 await 调用)的任何方法调用时中断执行,并立即执行当前代码分支之外的代码,一旦达到await ,执行将进入前一个async 这将一次又一次地重复,直到所有异步调用都完成并且它们的awaiters得到满足awaiters

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

所以我一直在阅读线程模型,并且 Async / Await 肯定会导致使用新线程(不一定创建 - 池在应用程序启动时创建它们)。 由调度程序决定是否需要新线程。 在我看来,对可等待函数的调用可能具有内部细节,这会增加调度程序使用另一个线程的机会; 仅仅是因为更多的工作意味着调度员有更多的机会/理由来分配工作。

WinRT 异步操作自动发生在线程池上。 通常你会从线程池调用,除了 UI 线程工作 .. Xaml/Input/Events。

在 Xaml/UI 线程上启动的异步操作将其结果传递回 [调用] UI 线程。 但是从线程池线程开始的异步操作结果会在完成发生的任何地方交付,这可能与您之前所在的线程不同。 这背后的原因是为线程池编写的代码很可能被编写为线程安全的并且也是为了效率,Windows 不必协商该线程切换。

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

我知道这似乎与一些关于 async/await 的文献相矛盾,但那是因为尽管 async/await 构造本身不是多线程的。 Awaitable 是调度程序可以跨线程划分工作和构造调用的机制或机制之一。

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

是什么真正帮助我解决了这个问题。

await关键字视为方法的return

之后的一切 - 都是“回调”。

该“回调”稍后执行。 在大多数情况下,甚至可能在同一线程上。 在某些情况下可以是不同的线程,但它仍然是ONE THREAD

PS 如果您的async方法在链的更深处有许多嵌套的async方法 - 好吧,它是一样的。 一路下来只是很多“来电者”和“回调”。 但它们都在一个线程上执行。 在这些嵌套调用的深处,在链的末端,有一个真正的异步 API(I/O 写入磁盘,或延迟,或网络请求或其他本质上“真正”异步的东西)可以做到异步通过 I/O 驱动程序和系统中断工作,并在完成后调用您的回调。

PPS 如果您正在调用的异步方法显式调用Task.RunTask.Yield那么情况就不同了。

使用 Async/Await 不一定会导致创建新线程。 但是使用 Async/Await 可能会导致创建一个新线程,因为 awaitable 函数可能会在内部生成一个新线程。 而且它经常这样做,这使得“不,它不会产生线程”在实践中几乎毫无用处。 例如,以下代码生成新线程。

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());
    ...
}

在 Java Spring Framework 的情况下,使用@Async注释的方法在单独的线程中运行。 引用官方指南( https://spring.io/guides/gs/async-method ) -

findUser 方法使用 Spring 的 @Async 注释进行标记,表明它应该在单独的线程上运行。 该方法的返回类型是 CompletableFuture 而不是 User,这是任何异步服务的要求。

当然,在后端它使用线程池和队列(异步任务等待线程返回池中)。

暂无
暂无

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

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