使用 async/await 会创建一个新线程吗?
Posted
技术标签:
【中文标题】使用 async/await 会创建一个新线程吗?【英文标题】:Does the use of async/await create a new thread? 【发布时间】:2015-01-31 16:01:20 【问题描述】:我是TPL 的新手,我想知道:C# 5.0 新增的异步编程支持(通过新的async
和await
关键字)与线程的创建有何关系?
具体来说,async/await
的使用是否会在每次使用它们时创建一个新线程?如果有许多嵌套方法使用async/await
,是否为每个方法创建一个新线程?
【问题讨论】:
取决于您使用await
调用的方法是如何实现的。您应该阅读blog.stephencleary.com/2013/11/there-is-no-thread.html 但请注意。 我们将深入研究。
否,例如见***.com/a/27071434/876814
相关/重复:async - stay on the current thread?
相关/重复:If async-await doesn't create any additional threads, then how does it make applications responsive?
【参考方案1】:
总之没有
来自Asynchronous Programming with Async and Await : Threads
async 和 await 关键字不会导致额外的线程 创建的。异步方法不需要多线程,因为异步 方法不在自己的线程上运行。该方法在当前运行 同步上下文并仅在线程上使用时间 方法处于活动状态。您可以使用 Task.Run 将 CPU 密集型工作移动到 后台线程,但后台线程对进程没有帮助 那只是在等待结果可用。
【讨论】:
这个问题我看了多少遍还是不明白。 “异步方法不需要多线程,因为异步方法不在自己的线程上运行” Ergo -> 另一个线程。不然怎么可能? 不是每个操作都需要一个线程。典型系统上有很多处理器/控制器,包括磁盘控制器、网卡控制器、GPU 等。它们只需要接收来自处理器的命令。然后他们继续执行命令并在完成时告诉处理器(通过中断或其他机制通知它)。在此之前,不涉及线程。发出线程可以进入睡眠状态,也可以进入可以重用的线程池。命令执行完毕后,程序的执行可能会被休眠线程或线程池线程继续执行。 如果 async/await 方法是一个 cpu 绑定方法 Task.Run 用于处理长时间运行的进程,在这种情况下 asycn 需要新线程,不是吗? @dudeNumber4 “一个异步方法不在它自己的线程上运行 -> 因此另一个线程”。不,它在同一个线程上运行!与调用它的方法相同的线程。它只是返回到该调用方法,以防它开始“等待”某些东西,以免浪费 CPU 周期。 @Bart CPU 周期与此 q/a 无关。【参考方案2】:使用 Async/Await 不一定会创建新线程。但是使用 Async/Await 可能会导致创建一个新线程,因为可等待函数可能会在内部产生一个新线程。它经常这样做,使得“不,它不会产生线程”这句话在实践中几乎毫无用处。例如,以下代码生成新线程。
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());
...
【讨论】:
@GavinWilliams 用于回调的线程是由TaskScheduler.Current
在调用await
时选择的,而不是您调用 await 的函数。它返回时在不同的线程上运行的事实与GetFileAsync
无关
@GavinWilliams - 在您展示的代码中,没有任何内容可以使其成为一个很好的示例如果读者没有具体了解GetFileAsync
的实现。这使得这是一个糟糕的例子。
@Enigmativity 我不会让你感到抱歉。我认为通常 GetFileAsync 或任何其他方法的用户不会对它的实现有具体的了解。我将它作为 async / await 使用导致创建线程的示例。它确实显示了这个结果。这是一种非常常见的异步方法,我建议它是一个典型的例子。是否有一些不典型的东西使它成为一个糟糕的例子?实际上,如果您使用 async/await,则必须小心管理代码所在的线程。
@GavinWilliams - 如果我给了你一个方法的签名,比如说Task<int> GetAgeAsync()
,你无法通过查看它来判断它是否会创建一个线程。你必须展示实现。所以你的例子是一样的。没有办法判断这是否创建了一个线程。如果你用GetFileAsync
或GetAgeAsync
的完整实现来展示你自己的例子,那么你可以证明它有或没有。
无法判断——这就是我要说的。您不知道是否会创建线程,在我的回答中我说..“使用 Async/Await 不一定会导致创建新线程。但是使用 Async/Await 可能会导致新线程线程被创建,因为等待函数可能在内部产生一个新线程。”确实如此,尽管似乎还有另一种机制可以创建线程,正如 Scott Chamberlain 所建议的那样,即 TaskSchedular 可以自行决定是否生成线程。【参考方案3】:
所以我一直在阅读线程模型,并且 Async / Await 肯定会导致使用新线程(不一定创建 - 池在应用程序启动时创建它们)。由调度程序决定是否需要新线程。正如我所看到的,对可等待函数的调用可能具有内部细节,这些细节会增加调度程序利用另一个线程的机会;仅仅是因为更多的工作意味着调度员有更多的机会/理由来分配工作。
WinRT 异步操作自动发生在线程池上。通常你会从线程池中调用,除了 UI 线程工作.. Xaml/Input/Events。
在 Xaml/UI 线程上启动的异步操作会将其结果传递回 [调用] UI 线程。但是从线程池线程开始的异步操作结果会在完成发生的任何地方传递,这可能与您之前所在的线程不同。这背后的原因是,为线程池编写的代码很可能被编写为线程安全的,而且也是为了效率,Windows 不必协商线程切换。
同样,作为对 OP 的回答,不一定要创建新线程,但您的应用程序可以并且将使用多个线程来完成异步工作。
我知道这似乎与一些关于 async / await 的文献相矛盾,但那是因为虽然 async / await 构造本身并不是多线程的。 Awaitables 是调度程序可以分配工作和跨线程构造调用的机制之一。
这是我目前关于异步和线程的知识的极限,所以我可能并不完全正确,但我确实认为了解等待对象和线程之间的关系很重要。
【讨论】:
我认为这是错误的。你把纯await DoAsync()
和await Task.Run(Do)
混在一起。后者将因为 Task.Run 而使用线程池,但不是因为等待。我的意思是,甚至官方文档都说The async and await keywords don't cause additional threads to be created.
。
@Blechdose 是的,据我了解,如果您只是在具有同步代码(单线程)的任务上使用 async / await 胶水,那很好,它将在调用时运行线。但是对于任何可等待的黑盒方法。你不知道是否会使用另一个线程。在等待之后,你永远不应该假设你在哪个线程上。【参考方案4】:
很抱歉迟到了。
我是 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
得到满足。
如果任何异步方法的 CPU 负载过重而没有调用异步方法,那么是的,您的系统将变得无响应,并且在当前任务完成之前不会调用所有剩余的异步方法。
【讨论】:
为什么说“编译器会在对异步方法的任何方法调用中中断执行(无论是否等待)”?微软表示:“如果 async 关键字修改的方法不包含 await 表达式或语句,则该方法同步执行”。在这种情况下, async 关键字根本不会中断执行流程。来源:docs.microsoft.com/fr-fr/dotnet/csharp/language-reference/… “(无论是否等待)”的措辞是错误的。现在修好了。谢谢指正。【参考方案5】:根据 MSDN:async keyword
异步方法同步运行,直到它到达其第一个等待表达式,此时该方法被挂起,直到等待的任务完成。与此同时,控制权返回给方法的调用者,如下一节中的示例所示。
这是一个检查它的示例代码:
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\ttxt");
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");
结果:
我们看到在另一个线程上执行之后的 Experiment() 方法的代码。
但如果我用自己的代码替换 Task.Delay(方法 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\ttxt");
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 创建的。
在这种情况下,我认为 Task.Delay
创建了线程,因此我可以得出结论 async/await 不会像 @Adriaan Stander 所说的那样创建新线程。
【讨论】:
谢谢。我认为它澄清了由于 Async Await 经常与 Task in TAP (Task Async Pattern) 一起使用而引起的一些混淆。因此,有些人错误地认为 Async Await 创建了一个新线程而不是 Task。据我了解,TAP 模式提供了一种更干净地管理多线程功能的方法。以上是关于使用 async/await 会创建一个新线程吗?的主要内容,如果未能解决你的问题,请参考以下文章
Oracle 托管驱动程序可以正确使用 async/await 吗?
操作系统:为什么IO操作不占用CPU却会导致进程阻塞?Web服务器每接收一个请求都会创建一个新的线程吗?Tomcat服务器工作原理?