异步方法的运行机制
基于这个代码片段:
public async Task<int> GetUrlContentLengthAsync()
{
var client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("https://docs.microsoft.com/dotnet");
DoIndependentWork();
string contents = await getStringTask;
return contents.Length;
}
void DoIndependentWork()
{
Console.WriteLine("Working...");
}
可以从前面的示例中了解几种做法。 从方法签名开始。 它包含 async 修饰符。 返回类型为 Task<int>(有关更多选项,请参阅“返回类型”部分)。 方法名称以 Async 结尾。 在方法的主体中,GetStringAsync 返回 Task<string>。 这意味着在 await 任务时,将获得 string (contents)。 在等待任务之前,可以通过 GetStringAsync 执行不依赖于 string 的工作。
密切注意 await 运算符。 它会暂停 GetUrlContentLengthAsync:
- 在
getStringTask完成之前,GetUrlContentLengthAsync无法继续。 - 同时,控件返回至
GetUrlContentLengthAsync的调用方。 - 当
getStringTask完成时,控件将在此处继续。 - 然后,
await运算符会从getStringTask检索string结果。
return 语句指定整数结果。 任何等待 GetUrlContentLengthAsync 的方法都会检索长度值。
如果 GetUrlContentLengthAsync 在调用 GetStringAsync 和等待其完成期间不能进行任何工作,则你可以通过在下面的单个语句中调用和等待来简化代码。
异步编程中最需弄清的是控制流是如何从方法移动到方法的。 下图可引导你完成此过程:
关系图中的数字对应于以下步骤,在调用方法调用异步方法时启动。
-
调用方法调用并等待
GetUrlContentLengthAsync异步方法。 -
GetUrlContentLengthAsync可创建 HttpClient 实例并调用 GetStringAsync 异步方法以下载网站内容作为字符串。 -
GetStringAsync中发生了某种情况,该情况挂起了它的进程。 可能必须等待网站下载或一些其他阻止活动。 为避免阻止资源,GetStringAsync会将控制权出让给其调用方GetUrlContentLengthAsync。GetStringAsync返回 Task<TResult>,其中TResult为字符串,并且GetUrlContentLengthAsync将任务分配给getStringTask变量。 该任务表示调用GetStringAsync的正在进行的进程,其中承诺当工作完成时产生实际字符串值。 -
由于尚未等待
getStringTask,因此,GetUrlContentLengthAsync可以继续执行不依赖于GetStringAsync得出的最终结果的其他工作。 该任务由对同步方法DoIndependentWork的调用表示。 -
DoIndependentWork是完成其工作并返回其调用方的同步方法。 -
GetUrlContentLengthAsync已运行完毕,可以不受getStringTask的结果影响。 接下来,GetUrlContentLengthAsync需要计算并返回已下载的字符串的长度,但该方法只有在获得字符串的情况下才能计算该值。因此,
GetUrlContentLengthAsync使用一个 await 运算符来挂起其进度,并把控制权交给调用GetUrlContentLengthAsync的方法。GetUrlContentLengthAsync将Task<int>返回给调用方。 该任务表示对产生下载字符串长度的整数结果的一个承诺。在调用方法中,处理模式会继续。 在等待结果前,调用方可以开展不依赖于GetUrlContentLengthAsync结果的其他工作,否则就需等待片刻。 调用方法等待GetUrlContentLengthAsync,而GetUrlContentLengthAsync等待GetStringAsync。 -
GetStringAsync完成并生成一个字符串结果。 字符串结果不是通过按你预期的方式调用GetStringAsync所返回的。 (记住,该方法已返回步骤 3 中的一个任务)。相反,字符串结果存储在表示getStringTask方法完成的任务中。 await 运算符从getStringTask中检索结果。 赋值语句将检索到的结果赋给contents。 -
当
GetUrlContentLengthAsync具有字符串结果时,该方法可以计算字符串长度。 然后,GetUrlContentLengthAsync工作也将完成,并且等待事件处理程序可继续使用。 在此主题结尾处的完整示例中,可确认事件处理程序检索并打印长度结果的值。 如果你不熟悉异步编程,请花 1 分钟时间考虑同步行为和异步行为之间的差异。 当其工作完成时(第 5 步)会返回一个同步方法,但当其工作挂起时(第 3 步和第 6 步),异步方法会返回一个任务值。 在异步方法最终完成其工作时,任务会标记为已完成,而结果(如果有)将存储在任务中。

浙公网安备 33010602011771号