【C#复习总结】 Async 和 Await 的异步编程

谈到异步,必然要说下阻塞,在知乎上看到了网友举的例子非常省动,在这里我引用下。

怎样理解阻塞非阻塞与同步异步的区别?

老张爱喝茶,废话不说,煮开水。

出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。1 老张把水壶放到火上,立等水开。(同步阻塞)老张觉得自己有点傻

2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。

3 老张把响水壶放到火上,立等水开。(异步阻塞)老张觉得这样傻等意义不大4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)老张觉得自己聪明了。

所谓同步异步,只是对于水壶而言。

普通水壶,同步;响水壶,异步。

虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。

同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。

所谓阻塞非阻塞,仅仅对于老张而言。

立等的老张,阻塞;看电视的老张,非阻塞。

情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

有了对异步的理解,接下来我们就进入主题,讲一下Async Await 。

关于这个话题阻塞非阻塞与同步异步,请看这篇文章讲的挺详细的:20分钟读懂进程线程、同步异步、阻塞非阻塞、并发并行

1 含义

C# 5 引入使用 Async Await 的异步编程,C# 中的 Async Await 关键字是异步编程的核心。 通过这两个关键字,可以使用 .NET Framework.NET Core Windows 运行时中的资源,轻松创建异步方法(几乎与创建同步方法一样轻松)。 使用 async 关键字定义的方法简称为“异步方法”。

作用

异步对可能会被屏蔽的活动(如 Web 访问)至关重要。 对 Web 资源的访问有时很慢或会延迟。 如果此类活动在同步过程中被屏蔽,整个应用必须等待。 在异步过程中,应用程序可继续执行不依赖 Web 资源的其他工作,直至潜在阻止任务完成。

下表显示了异步编程提高响应能力的典型区域。 列出的 .NET Windows 运行时 API 包含支持异步编程的方法。

应用程序区域

包含异步方法的 .NET 类型

包含异步方法的 Windows 运行时类型

Web 访问

HttpClient

SyndicationClient

使用文件

StreamWriter, StreamReader, XmlReader

StorageFile

使用图像

MediaCapture, BitmapEncoder, BitmapDecoder

WCF 编程

同步和异步操作

 

由于所有与用户界面相关的活动通常共享一个线程,因此,异步对访问 UI 线程的应用程序来说尤为重要。 如果任何进程在同步应用程序中受阻,则所有进程都将受阻。 你的应用程序停止响应,因此,你可能在其等待过程中认为它已经失败。

使用异步方法时,应用程序将继续响应 UI。 例如,你可以调整窗口的大小或最小化窗口;如果你不希望等待应用程序结束,则可以将其关闭。

3 用法及示例

C# 中的 Async Await 关键字是异步编程的核心。 通过这两个关键字,可以使用 .NET Framework.NET Core Windows 运行时中的资源,轻松创建异步方法(几乎与创建同步方法一样轻松)。 使用 async 关键字定义的异步方法简称为“异步方法”。

代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;


// 添加Using指令
using System.Net.Http;

namespace async_await
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        /// <summary>
        /// 按钮
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void StartButton_Click_Click(object sender, EventArgs e)
        {
       
            int contentLength = await AccessTheWebAsync();

            resultsTextBox.Text +=
                String.Format("
Length of the downloaded string: {0}.
", contentLength);
        }    
        /// <summary>
        /// 签名要注意3点
        /// 1.必须要有异步修饰符async
        /// 2.返回类型必须是 Task 或者 是 Task<T>
        /// 3.方法结尾必须是 Async
        /// </summary>
        /// <returns></returns>
        async Task<int> AccessTheWebAsync()
        {
            //添加Http客户端声明引用
            HttpClient client = new HttpClient();

            //GetStringAsync返回一个字符串,如果不异步的话,此步可能浪费很长时间

            Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
 
            //做其他的事
            DoIndependentWork();

            /*等待操作员暂停访问AccessTheWebAsync ;直到GetStringAsync,才可以继续;与此同时,控制权返回给AccessTheWebAsync;
             * 当GetStringAsync 完成时,控制从此处继续  */
            string urlContents = await getStringTask;

            //返回长度
            return urlContents.Length;
        }

        void DoIndependentWork()
        {
            resultsTextBox.Text += "Working . . . . . . .
";
        }
    }
}

异步方法的运行机制

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

关系图中的数值对应于以下步骤。

事件处理程序调用并等待 AccessTheWebAsync 异步方法。

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

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

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

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

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

6 AccessTheWebAsync 已用完工作,可以不受 getStringTask 的结果影响。 接下来,AccessTheWebAsync 需要计算并返回该下载字符串的长度,但该方法仅在具有字符串时才能计算该值。

因此,AccessTheWebAsync 使用一个 await 运算符来挂起其进度,并把控制权交给调用 AccessTheWebAsync 的方法。 AccessTheWebAsync Task(Of Integer) Task<int> 返回至调用方。 该任务表示对产生下载字符串长度的整数结果的一个承诺

说明:

如果 GetStringAsyncAccessTheWebAsync 等待前完成,则控件会保留在 AccessTheWebAsync 中。 如果异步调用过程 (getStringTask) 已完成,并且 AccessTheWebSync 不必等待最终结果,则挂起然后返回到 AccessTheWebAsync 将造成成本浪费。

在调用方内部(此示例中的事件处理程序),处理模式将继续。 在等待结果前,调用方可以开展不依赖于 AccessTheWebAsync 结果的其他工作,否则就需等待片刻。事件处理程序等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync

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

8 AccessTheWebAsync 具有字符串结果时,该方法可以计算字符串长度。 然后,AccessTheWebAsync 工作也将完成,并且等待事件处理程序可继续使用。 

参考文献:【MSDN】【知乎

推荐阅读:【Async/Await异步编程中的最佳做法】【走进异步编程的世界 - 开始接触 async/await】【Async and Await

原文地址:https://www.cnblogs.com/mhq-martin/p/8973792.html