异步方法的运行机制

基于这个代码片段:

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 和等待其完成期间不能进行任何工作,则你可以通过在下面的单个语句中调用和等待来简化代码。

 

异步编程中最需弄清的是控制流是如何从方法移动到方法的。 下图可引导你完成此过程:

异步控制流的跟踪导航

关系图中的数字对应于以下步骤,在调用方法调用异步方法时启动。

  1. 调用方法调用并等待 GetUrlContentLengthAsync 异步方法。

  2. GetUrlContentLengthAsync 可创建 HttpClient 实例并调用 GetStringAsync 异步方法以下载网站内容作为字符串。

  3. GetStringAsync 中发生了某种情况,该情况挂起了它的进程。 可能必须等待网站下载或一些其他阻止活动。 为避免阻止资源,GetStringAsync 会将控制权出让给其调用方 GetUrlContentLengthAsync

    GetStringAsync 返回 Task<TResult>,其中 TResult 为字符串,并且 GetUrlContentLengthAsync 将任务分配给 getStringTask 变量。 该任务表示调用 GetStringAsync 的正在进行的进程,其中承诺当工作完成时产生实际字符串值。

  4. 由于尚未等待 getStringTask,因此,GetUrlContentLengthAsync 可以继续执行不依赖于 GetStringAsync 得出的最终结果的其他工作。 该任务由对同步方法 DoIndependentWork 的调用表示。

  5. DoIndependentWork 是完成其工作并返回其调用方的同步方法。

  6. GetUrlContentLengthAsync 已运行完毕,可以不受 getStringTask 的结果影响。 接下来,GetUrlContentLengthAsync 需要计算并返回已下载的字符串的长度,但该方法只有在获得字符串的情况下才能计算该值。

    因此,GetUrlContentLengthAsync 使用一个 await 运算符来挂起其进度,并把控制权交给调用 GetUrlContentLengthAsync 的方法。 GetUrlContentLengthAsync 将 Task<int> 返回给调用方。 该任务表示对产生下载字符串长度的整数结果的一个承诺。在调用方法中,处理模式会继续。 在等待结果前,调用方可以开展不依赖于 GetUrlContentLengthAsync 结果的其他工作,否则就需等待片刻。 调用方法等待 GetUrlContentLengthAsync,而 GetUrlContentLengthAsync 等待 GetStringAsync

  7. GetStringAsync 完成并生成一个字符串结果。 字符串结果不是通过按你预期的方式调用 GetStringAsync 所返回的。 (记住,该方法已返回步骤 3 中的一个任务)。相反,字符串结果存储在表示 getStringTask 方法完成的任务中。 await 运算符从 getStringTask 中检索结果。 赋值语句将检索到的结果赋给 contents

  8. 当 GetUrlContentLengthAsync 具有字符串结果时,该方法可以计算字符串长度。 然后,GetUrlContentLengthAsync 工作也将完成,并且等待事件处理程序可继续使用。 在此主题结尾处的完整示例中,可确认事件处理程序检索并打印长度结果的值。 如果你不熟悉异步编程,请花 1 分钟时间考虑同步行为和异步行为之间的差异。 当其工作完成时(第 5 步)会返回一个同步方法,但当其工作挂起时(第 3 步和第 6 步),异步方法会返回一个任务值。 在异步方法最终完成其工作时,任务会标记为已完成,而结果(如果有)将存储在任务中。

posted on 2021-11-16 15:56  马斯塔剑  阅读(40)  评论(0编辑  收藏  举报

导航