4. 异步程序中的控制流 (C#)

可以使用 async 和 await 关键字更加轻松地编写和维护异步程序。 但是,如果不了解程序的运行方式,结果可能会让你大吃一惊。 此主题通过一个简单的异步程序跟踪控制流,以显示控制从一种方法移动到另一种方法的情况,以及每次所传输的信息。

一般情况下,使用 async (C#) 修饰符标记包含异步代码的方法。 在使用 async 修饰符标记的方法中,可以使用 await (C#) 运算符来指定暂停该方法以等待调用的异步进程完成的位置。 有关详细信息,请参阅使用 Async 和 Await 的异步编程 (C#)。

下面的示例使用异步方法以字符串的形式下载指定网站的内容,并显示该字符串的长度。 此示例包含以下两种方法。

  • startButton_Click,它调用 AccessTheWebAsync 并显示结果。

  • AccessTheWebAsync,它以字符串的形式下载网站的内容,并返回该字符串的长度。 AccessTheWebAsync 使用异步 HttpClient 方法 GetStringAsync(String) 来下载内容。

编号的显示行显示在整个程序的策略点处,以帮助你了解程序的运行方式和说明被标记的每个点处发生的情况。 显示行标记为“1”到“6”。 该标签表示程序到达这些代码行的顺序。

下面的代码显示该程序的概要。

每个标记位置(“1”到“6”)显示有关该程序的当前状态的信息。 将生成以下输出:

ONE:   Entering startButton_Click.
           Calling AccessTheWebAsync.

TWO:   Entering AccessTheWebAsync.
           Calling HttpClient.GetStringAsync.

THREE: Back in AccessTheWebAsync.
           Task getStringTask is started.
           About to await getStringTask & return a Task<int> to startButton_Click.

FOUR:  Back in startButton_Click.
           Task getLengthTask is started.
           About to await getLengthTask -- no caller to return to.

FIVE:  Back in AccessTheWebAsync.
           Task getStringTask is complete.
           Processing the return statement.
           Exiting from AccessTheWebAsync.

SIX:   Back in startButton_Click.
           Task getLengthTask is finished.
           Result from AccessTheWebAsync is stored in contentLength.
           About to display contentLength and exit.

Length of the downloaded string: 33946.

5. 取消任务和处理已完成任务

可以使用由 Task 类型提供的方法和属性将精度和灵活性添加到异步应用程序。 本部分中的主题介绍使用 CancellationToken 的示例和一些重要的 Task 方法,例如 Task.WhenAll 和 Task.WhenAny。

使用 WhenAny 和 WhenAll 可以更轻松地启动多个任务并通过监视单个任务待其完成。

  • 集合中的任何任务完成时,WhenAny 将返回完成的任务。

  • 集合中的所有任务完成时,WhenAll 将返回完成的任务。

5.1. 取消异步任务或任务列表

如果不想等待异步应用程序完成,可以设置一个按钮用来取消它。 通过遵循本主题中的示例,可以为下载一个或一组网站内容的应用程序添加一个取消按钮。

5.2. 在一段时间后取消异步任务

如果不希望等待操作结束,可使用 CancellationTokenSource.CancelAfter 方法在一段时间后取消异步操作。 此方法会计划取消未在 CancelAfter 表达式指定的时间段内完成的任何关联任务。

5.3. 在完成一个异步任务后取消剩余任务

通过结合使用 Task.WhenAny 方法和 CancellationToken,可在一个任务完成时取消所有剩余任务。 WhenAny 方法采用任务集合中的一个参数。 该方法启动所有任务,并返回单个任务。 当集合中任意任务完成时,完成单个任务。

5.4. 启动多个异步任务并在其完成时进行处理

通过使用 Task.WhenAny,可以同时启动多个任务,并在它们完成时逐个对它们进行处理,而不是按照它们的启动顺序进行处理。

6. 处理异步应用中的重新进入

在应用中包含异步代码时,应考虑并且可以阻止重新进入(指在异步操作完成之前重新进入它)。 如果不识别并处理重新进入的可能性,则它可能会导致意外结果。