异步方法是否必须由操作系统支持,还是异步程序级别的特性?

6

有时候教程会指导读者实现自己的异步方法,例如下面这段代码:

async public static Task GetHttpResponseAsync()
{
   using (HttpClient httpClient = new HttpClient())
   {
       HttpResponseMessage response = await httpClient.GetAsync(...);
       Console.WriteLine(response.Something);
   }            
}

我明白的是异步工作的一般原理,但是没有任何教程解释内部实现方式。
httpClient.GetAsync(...);

这对于理解异步代码的工作原理非常重要。让我感到好奇的是,GetAsync(或其他异步方法)的内部操作是否被注册在某种容器中以执行此代码?异步方法是否必须由操作系统支持(例如,它使用Windows API)?如果我想实现自己的异步文件下载器(从磁盘上而且没有.NET框架的重要部分),我应该如何实现它?我需要在某个地方注册我的方法以供进一步调用吗?
对我来说很清楚,编译器在内部创建状态机,并在DoSomething()方法完成后再次调用此状态机以恢复执行await后的代码。
另外,我不清楚的是异步代码如何在同一线程上运行。我认为维护状态机必须在同一线程上进行,但httpClient.GetAsync()中的代码如何在同一线程上运行而不会中断其他操作(例如GUI)呢?在所有情况下,必须有某些东西使此代码在单独的线程上运行。我错了吗?我错过了什么?
我的问题的额外解释:就我所知和理解的JavaScript,异步方法通过将它们注册在某种容器中(该容器在单独的线程上逐个运行它们)来运行,该容器执行此方法。方法执行完成后,结果会返回到用户上下文中。我想知道这里的工作方式是否相同?

async/await 是一种程序级别的特性,使得编写与可等待对象进行交互的代码更加人性化。然而,异步本身是您的程序与任何实体进行通信的接口的一个特性。同步接口会让您等待所需的任何响应,而异步接口则接受您的请求并提供了一种机制,在准备好时回调您。异步接口很好地转换为可等待对象,而同步接口则不行。 - Asad Saeeduddin
@AsadSaeeduddin 所以,必须有一个在单独线程上运行代码的东西,然后强制用户线程(例如UI线程)再次调用异步方法来切换状态机并运行剩余的代码? - Puchacz
通过HTTP请求,"分离线程"可能是一个数百万英里远的服务器。您的程序不会消耗任何计算机资源(甚至不在另一个线程中),并且可以自由地处理其他事情,例如响应用户输入。关于"强制用户线程",不是这样的:异步/等待不是抢占式的。异步/等待可以完全不依赖于任何可用的线程进行实现。它只是一种转换机制,使您可以将方法分解为步骤并使用继续传递而无需考虑它。 - Asad Saeeduddin
1个回答

10
简言之,真正的异步必须在操作系统层面提供。正如你所注意到的,async/await是一种语言级别的功能,它使用编译器生成的状态机将你的方法分解成可以异步运行的片段,但它依赖于操作系统原语(中断、线程)来实际以异步方式执行此工作。
然而,重要的是要注意,通常不会创建线程来处理您的异步操作。我将推荐这篇写得非常好的文章来解释为什么会这样: http://blog.stephencleary.com/2013/11/there-is-no-thread.html 在Windows上,执行异步工作的主要机制是使用I/O完成端口。这是一个Windows API,许多.NET类型包括你正在使用的HttpClient在内都在底层使用它。
请注意,对于非I/O操作,您也可以始终使用线程,或者更好的选择是使用线程池API执行异步完成的后台工作。
Windows API中的ReadFile(或较新的ReadFileEx)函数是设计用于异步I/O。当您调用ReadFile时,可以使用FILE_FLAG_OVERLAPPED标志并将OVERLAPPED结构传递给lpOverlapped参数,以启用异步读取。我建议您使用此API来设计文件下载器。
总之:

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接