C#异步编程由浅入深(二)之Async/Await的使用
作者:白烟染黑墨 发布时间:2022-10-14 16:45:20
  考虑到直接讲实现一个类Task库思维有点跳跃,所以本节主要讲解Async/Await的本质作用(解决了什么问题),以及Async/Await的工作原理。实现一个类Task的库则放在后面讲。首先回顾一下上篇博客的场景。
class Program
{
public static string GetMessage()
{
return Console.ReadLine();
}
public static string TranslateMessage(string msg)
return msg;
public static void DispatherMessage(string msg)
switch (msg)
{
case "MOUSE_MOVE":
{
OnMOUSE_MOVE(msg);
break;
}
case "MOUSE_DOWN":
OnMouse_DOWN(msg);
default:
break;
}
public static void OnMOUSE_MOVE(string msg)
Console.WriteLine("开始绘制鼠标形状");
public static int Http()
Thread.Sleep(1000);//模拟网络IO延时
return 1;
public static void HttpAsync(Action<int> action,Action error)
//这里我们用另一个线程来实现异步IO,由于Http方法内部是通过Sleep来模拟网络IO延时的,这里也只能通过另一个线程来实现异步IO
//但记住,多线程是实现异步IO的一个手段而已,它不是必须的,后面会讲到如何通过一个线程来实现异步IO。
Thread thread = new Thread(() =>
try
{
int res = Http();
action(res);
}
catch
error();
});
thread.Start();
public static Task<int> HttpAsync()
return Task.Run(() =>
return Http();
public static void OnMouse_DOWN(string msg)
HttpAsync()
.ContinueWith(t =>
if(t.Status == TaskStatus.Faulted)
}else if(t.Status == TaskStatus.RanToCompletion)
Console.WriteLine(1);
//做一些工作
})
if (t.Status == TaskStatus.Faulted)
else if (t.Status == TaskStatus.RanToCompletion)
Console.WriteLine(2);
Console.WriteLine(3);
});
static void Main(string[] args)
while (true)
string msg = GetMessage();
if (msg == "quit") return;
string m = TranslateMessage(msg);
DispatherMessage(m);
}
  在OnMouse_DOWN这个处理函数中,我们使用Task的ContinueWith函数进行链式操作,解决了回调地狱问题,但是总感觉有点那么不爽,我们假想有个关键字await它能实现以下作用:首先await必须是Task类型,必须是Task类型的(其实不是必要条件,后面会讲到)原因是保证必须有ContinueWith这个函数,如果Task没有返回值,则把await后面的代码放到Task中的ContinueWith函数体内,如果有返回值,则把Await后的结果转化为访问Task.Result属性,文字说的可能不明白,看下示例代码
//无返回值转换前
public async void Example()
{
Task t = Task.Run(() =>
{
Thread.Sleep(1000);
});
await t;
//做一些工作
}
//无返回值转换后
public void Example()
t.ContinueWith(task =>
//做一些工作
//有返回值转换前
Task<int> t = Task.Run<int>(() =>
return 1;
int res = await t;
//使用res做一些工作
//有返回值转换后
//使用task.Result做一些工作
  看起来不错,但至少有以下问题,如下:
该种转换方法不能很好的转换Try/Catch结构
在循环结构中使用await不好转换
该实现与Task类型紧密联系
  一二点是我自己认为的,但第三点是可以从扩展async/await这点被证明的。但无论怎样,async/await只是对方法按照一定的规则进行了变换而已,它并没有什么特别之处,具体来讲,就是把Await后面要执行的代码放到一个类似ContinueWith的函数中,在C#中,它是以状态机的形式表现的,每个状态都对应一部分代码,状态机有一个MoveNext()方法,MoveNext()根据不同的状态执行不同的代码,然后每个状态部分对应的代码都会设置下一个状态字段,然后把自身的MoveNext()方法放到类似ContinueWith()的函数中去执行,整个状态机由回调函数推动。我们尝试手动转换以下async/await方法。
public static Task WorkAsync()
{
return Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Done!");
});
}
public static async void Test()
{
Console.WriteLine("步骤1");
await WorkAsync();
Console.WriteLine("步骤2");
await WorkAsync();
Console.WriteLine("步骤3");
}
  手动写一个简单的状态机类
public class TestAsyncStateMachine
{
public int _state = 0;
public void Start() => MoveNext();
public void MoveNext()
{
switch(_state)
{
case 0:
{
goto Step0;
}
case 1:
goto Step1;
default:
Console.WriteLine("步骤3");
return;
}
Step0:
Console.WriteLine("步骤1");
_state = 1;
WorkAsync().ContinueWith(t => this.MoveNext());
return;
Step1:
_state = -1;
Console.WriteLine("步骤2");
}
}
  而Test()方法则变成了这样
public static void Test()
{
new TestAsyncStateMachine().Start();
}
  注意Test()方法返回的是void,这意味这调用方将不能await Test()。如果返回Task,这个状态机类是不能正确处理的,如果要正确处理,那么状态机在Start()启动后,必须返回一个Task,而这个Task在整个状态机流转完毕后要变成完成状态,以便调用方在该Task上调用的ContinueWith得以继续执行,而就Task这个类而言,它是没有提供这种方法(内部有,但没有对外暴露)来主动控制Task的状态的,这个与JS中的Promise不同,JS里面用Reslove函数来主动控制Promise的状态,并导致在该Promise上面的Then链式调用得以继续完成,而在C#里面怎么做呢?既然使用了状态机来实现async/await,那么在转换一个返回Task的函数时肯定会遇到,怎么处理?后面讲。
  首先解决一下与Task类型紧密联系这个问题。
  从状态机中可以看到,主要使用到了Task中的ContinueWith这个函数,它的语义是在任务完成后,执行回调函数,通过回调函数拿到结果,这个编程风格也叫做CPS(Continuation-Passing-Style, 续体传递风格),那么我们能不能把这个函数给抽象出来呢?语言开发者当然想到了,它被抽象成了一个Awaiter因此编译器要求await的类型必须要有GetAwaiter方法,什么样的类型才是Awaiter呢?编译器规定主要实现了如下几个方法的类型就是Awaiter:
必须继承INotifyCompletion接口,并实现其中的OnCompleted(Action continuation)方法
必须包含IsCompleted属性
必须包含GetResult()方法
  第一点好理解,第二点的作用是热路径优化,第三点以后讲。我们再改造一下我们手动写的状态机。
public class TestAsyncStateMachine
{
public int _state = 0;
public void Start() => MoveNext();
public void MoveNext()
{
switch(_state)
{
case 0:
{
goto Step0;
}
case 1:
goto Step1;
default:
Console.WriteLine("步骤3");
return;
}
Step0:
Console.WriteLine("步骤1");
_state = 1;
TaskAwaiter taskAwaiter;
taskAwaiter = WorkAsync().GetAwaiter();
if (taskAwaiter.IsCompleted) goto Step1;
taskAwaiter.OnCompleted(() => this.MoveNext());
return;
Step1:
_state = -1;
Console.WriteLine("步骤2");
if (taskAwaiter.IsCompleted) MoveNext();
}
}
  可以看到去掉了与Task中ContinueWith的耦合关系,并且如果任务已经完成,则可以直接执行下个任务,避免了无用的开销。
  因此我们可以总结一下async/await:
async/await只是表示这个方法需要编译器进行特殊处理,并不代表它本身一定是异步的。
Task类中的GetAwaiter主要是给编译器用的。
  第一点我们可以用以下例子来证明,有兴趣的朋友可以自己去验证以下,以便加深理解。
//该类型包含GetAwaiter方法,且GetAwaiter()返回的类型包含三个必要条件
public class MyAwaiter : INotifyCompletion
{
public void OnCompleted(Action continuation)
{
continuation();
}
public bool IsCompleted { get; }
public void GetResult()
public MyAwaiter GetAwaiter() => new MyAwaiter();
}
  一个测试函数,注意必须返回void
public static async void AwaiterTest()
{
await new MyAwaiter();
Console.WriteLine("Done");
}
  可以看到这是完全同步进行的。
来源:https://www.cnblogs.com/hkfyf/p/14641844.html


猜你喜欢
- 一、前言:TCP原理简介首先,保证文章完整性,TCP的理论原理还是需要简介一下,略显枯燥๑乛◡乛๑。TCP(传输控制协议,Transmiss
- 最近用到一些字符串加密,而.net中提供的加密算法中用起来比较复杂,便简单的封装了一下,方便日后使用。public class Encryp
- 绑定(Binding)元素介绍首先,盗用张图。这图形象的说明了Binding的机理。此处主要介绍的绑定类是System.Windows.Da
- 研发背景公司安全部目前针对内部系统的网络访问日志的安全审计,大部分都是T+1时效,每日当天,启动Python编写的定时任务,完成昨日的日志审
- 前言这篇文章主要介绍Spring Boot的统一功能处理模块,也是AOP的实战环节。1.用户登录权限效验在学习Spring AOP之前,用户
- 背景之前和同事讨论一个问题,他们公司调研中发现forEach的速度比for的速度慢,当刚听到这个结论的时候有点诧异。因为之前看过国外的文章和
- 使用Post添加数据到数据库出现方块乱码解决方法,在web.xml里最前面添加过滤器,代码如下,放在最前面,因为有优先级,要首先拦截<
- 先新建一个文件夹kun,kun就是类所在的package。新建一个java文件。HelloWorld.java的代码如下:package k
- 1、问题引入我们已经完成了后台系统的登录功能开发,但是目前还存在一个问题,就是用户如果不登录,直接访问系统首页面,照样可以正常访问。很明显,
- 我们都知道可以用爬虫来找寻一些想要的数据,除了可以使用python进行操作,我们最近学习的java同样也支持爬虫的运行,本篇小编就教大家用j
- 前言:来这家公司上班后,开始使用Git作为项目版本控制系统,由于以前用的是SVN,所以对Git也就简单学习了一下。但是,实践出真知,当开始使
- 前言Date 类Date 类表示系统特定的时间戳,可以精确到毫秒。Date 对象表示时间的默认顺序是星期、月、日、小时、分、秒、年。构造方法
- 本文实例讲述了Android4.4电池低电量告警提示原理与实现方法。分享给大家供大家参考,具体如下:之前版本的电池电量低是通过发送 inte
- 前言Android提供了很多种保存应用程序数据的方法。其中一种就是用SharedPreferences对象来保存我们私有的键值(key-va
- 希尔排序是插入排序的一种,又称"缩小增量排序”,是插入排序算法的一种更高效的改进版本。希尔排序原理1.选定一个增长量h,按照增长量
- 1. 实验目的: 使用线程池的时候,有时候需要考虑服务器的最大线程数目和程序最快
- java 网络编程java.net 类 InetAddress 此类表示互联网协议 (IP) 地址。 会抛出异常 UnknownHostEx
- 微信是现在比较流行的应用了,在各大安卓市场几乎都是名列前茅了。说实话不得不羡慕腾讯庞大的用户群体,只要腾讯敢做,就会有很多人去用。废话不多说
- 本文实例讲述了C#按字节数截取字符串并在后面加上省略号...的方法,这是一个自定义的C#函数,函数的使用说明如下:<param nam
- 最近项目中需要用到IO流来读取图片以提供前台页面展示,由于以前一直是用url路径的方式进行图片展示,一听说要项目要用IO流读取图片感觉好复杂