已完成的await会在新线程上继续其自身的异步方法吗?
已完成的await会在新线程上继续其自身的异步方法吗?
我了解async/await的基本原理,但我经常看到某些情况下await不会创建一个新的线程。我能理解这点,因为await可能会使用硬盘I/O、网络卡或其他不需要新线程的CPU之外的资源。
我想要澄清的是,在await完成后,剩余的异步函数是否在一个新的线程上执行?根据我自己的内部测试,我看到确实是这样的,就像下面的代码所示:
public static class Program { private static void Main(string[] args) { Thread.CurrentThread.Name = "ThisThread"; AnAsyncMethod(); while (true) { print("External While loop. Current Thread: " + Thread.CurrentThread.Name); Thread.Sleep(200); } } public static async void AnAsyncMethod() { for (int i = 0; i < 10; i++) { FakeWork(); print("doing fake pre-await work. Current Thread: " + Thread.CurrentThread.Name); } await Task.Run(FakeWork); for (int i = 0; i < 10; i++) { FakeWork(); print("doing fake post-await work. Current Thread: " + Thread.CurrentThread.Name); } print("hello"); } public static void FakeWork() { Thread.Sleep(100); } }
从这个例子来看,异步方法在遇到第一个await之前都使用同一个线程,在遇到await之后,控制权返回给调用者,程序继续执行。当await完成后,会启动一个新的线程来继续异步方法中的执行,而不是让之前的线程继续执行。这个新线程与之前的线程并发执行,而之前的线程则被用于其他用途。
这样理解对吗?
问题的原因是在使用async/await时,await操作完成后,是否会在新的线程上继续执行其所属的异步方法。解决方法是使用Task.Run()将耗时的CPU绑定操作放入线程池中,并在await操作后继续执行。
在给出的代码中,首先将当前线程命名为"ThisThread",然后调用AnAsyncMethod()方法。在AnAsyncMethod()中,首先进行一些假的前await工作,并输出当前线程的信息。然后使用await关键字等待一个Task.Run()方法,该方法会将FakeWork()方法放入线程池,并返回一个Task句柄。FakeWork()最终会在下一个可用的线程上并发运行。接着进行一些假的后await工作,并输出当前线程的信息。最后输出"hello"。
通过运行代码,可以得到以下输出结果:
ThisThread- 1
doing fake pre-await work. Current Thread: ThisThread- 1
doing fake pre-await work. Current Thread: ThisThread- 1
...
doing fake pre-await work. Current Thread: ThisThread- 1
Caller of Fakework method is AnAsyncMethod. Current Thread: ThisThread- 1
Caller of Fakework method is AnAsyncMethod. Current Thread: ThisThread- 1
...
Caller of Fakework method is AnAsyncMethod. Current Thread: ThisThread- 1
hello
可以看到,await操作后的输出在一个单独的线程上执行。这是因为在调用Task.Run()时,将耗时的CPU绑定操作FakeWork()放入线程池,并返回一个Task句柄。FakeWork()最终会在下一个可用的线程上并发运行。根据默认调度算法,async/await的任务会在线程池上调度,可能使用相同的线程,也可能在新线程上运行。
以上是对问题的原因和解决方法的整理。通过将耗时的操作放入线程池中,并使用await关键字等待其完成,可以在异步方法中实现并发执行。
在这个问题中,问题的出现是因为在控制台应用程序和WinForms应用程序上的行为是不同的。在控制台应用程序中,默认情况下,继续运行在默认的TaskScheduler上,该TaskScheduler在可用的Worker Thread Pool上运行Task的继续。而在WinForms中,继续会被提交到UI线程。
在给出的示例中,我们可以看到代码的实际执行线程会跳来跳去,我在我的系统上看到了六个不同的线程,你可能会有不同的结果。但重要的是,第一个Console.WriteLine()始终在主线程上运行,即线程1。
在控制台应用程序中,你不需要使用Task.Run()来让你的CPU密集型任务在不同的线程池上运行。只需等待一个Task即可,例如:
await Task.Yield();
这样就可以确保在一个新的线程上继续执行异步方法。