异步方法的运行机制
基于这个代码片段:
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 步),异步方法会返回一个任务值。 在异步方法最终完成其工作时,任务会标记为已完成,而结果(如果有)将存储在任务中。