2019-8-31-C#-await-高级用法
title | author | date | CreateTime | categories |
---|---|---|---|---|
C# await 高级用法
|
lindexi
|
2019-08-31 16:55:58 +0800
|
2018-2-13 17:23:3 +0800
|
C# await dotnet
|
本文告诉大家 await 的高级用法,包括底层原理。
昨天看到太子写了一段代码,我开始觉得他修改了编译器,要不然下面的代码怎么可以编译通过
await "林德熙逗比";
需要知道,基本可以添加 await 都是可以等待的类型,如 Task 。如果一个类需要可以被等待,那么这个类必须满足以下条件
类里有一个 GetAwaiter 函数
GetAwaiter 有返回值,返回值需要继承 INotifyCompletion 并且有
bool IsCompleted { get; }
,GetResult()
,void OnCompleted(Action continuation)
定义
参见:如何实现一个可以用 await 异步等待的 Awaiter - walterlv
但是上面的代码使用的是一个字符串,什么时候可以修改继承字符串?
先让我来说下 await 原理,因为知道了原理,上面的代码实现很简单。看完了本文,你就会知道如何让几乎所有类型包括 int 、string 、自定义的类都支持 await 。
如果真的不想看原理,那么请直接调到文章的最后,看到最后很快就知道如何做。
原理
在 .net 4.5 之后,框架默认提供 async 和 await 的语法糖,这时千万不要认为进入 await 就会进入一个新的线程,实际上不一定会进入一个新的线程才会调用 await 。
那么 await 的语法糖写的是什么?实际上就是以前的 Begin xx 和 End xx 的语法糖。
古时候的写法:
foo.Beginxx(); foo.Endxx(传入委托);
这样大家就无法在一个流程写完,需要分为两个东西,而在 Continus with 下,就需要传入委托。如果委托里又使用了异步,那么又需要传入委托
task.ContinueWith(_ =>
{
Task t1 = new Task(() => { });
t1.ContinueWith((t2) =>
{
//可以看到如果进入很多的委托
});
});
所以这时就使用了 await ,可以让大家按照顺序写。
await task;
Task t1 = new Task(() => { });
await t1;
//可以看到这时不需要进入委托
实际上 await 是在编译时支持的,请看进阶篇:以IL为剑,直指async/await - 布鲁克石 - 博客园
而且千万不要认为 await 一定会进入一个新的线程,实际上他只是把需要写在多处的代码,可以按照流写下载,和写同步代码一样。如果感兴趣 await 不一定会进入一个新的线程请看 There Is No Thread
使用
因为 await 需要找到一个 GetAwaiter 函数,这个函数即使是扩展方法也可以,所以其实上面的代码是这样写的
public static class KvpbamjhKsvm
{
public static HeabdsdnbKevx GetAwaiter(this string str)
{
return new HeabdsdnbKevx();
}
} public class HeabdsdnbKevx : INotifyCompletion
{
public bool IsCompleted { get; } public void GetResult()
{
} /// <inheritdoc />
public void OnCompleted(Action continuation)
{
}
}
HeabdsdnbKevx 就是一个可以等待的类型
现在就可以写出下面的代码
private static void Main(string[] args)
{
DdngSiwchjux();
} private static async void DdngSiwchjux()
{
await "林德熙逗比";
}
当然,上面的这个代码可以运行,不过不会返回什么。下面让我加上一句代码。
private static void Main(string[] args)
{
DdngSiwchjux();
} private static async void DdngSiwchjux()
{
await "林德熙逗比";
Console.WriteLine("csdn");
}
这时可以看到,Console.WriteLine("csdn");
不会运行,因为这时如果在 OnCompleted
函数打断点就可以看到,执行await "林德熙逗比"
之后就进入OnCompleted
函数。从上面的原理可以知道,这个函数传入的参数就是两个await
或 await
和函数结束之间的代码。如果需要让Console.WriteLine("csdn");
运行,那么只需要在OnCompleted
运行参数
public void OnCompleted(Action continuation)
{
continuation();
}
但是作为一个挖坑专业的大神,怎么可以就扩展 string ,下面我把 int 进行扩展
public static class KvpbamjhKsvm
{
public static HeabdsdnbKevx GetAwaiter(this int dxpbnzSce)
{
return new HeabdsdnbKevx();
}
}
随意写一个值,然后进行等待
现在我准备在 object 加一个扩展方法,所有类型都可以等待,然后把这个扩展方法的 namespace 写为 System ,这样大家就不知道这个是我写的,过了一年我就告诉大家这是 C# 的特性,所有的类都可以等待。但是这个特性需要开光才可以使用,你们直接建的项目没有开光所以没法使用这个特性。
等待和不等待的区别
虽然很多时候从原理上看,等待和不等待只是调用时机的问题。但是依旧遇到一些小伙伴一直以为全部的异步方法都需要await
,看到我写了没有直接await
的代码觉得很诡异,所以我在这里做个实验给大家看。
下面的代码是最常见的代码,在 async Task
的方法使用 await
,这样就会等待这个方法完成,代码就和同步代码一样。
await GagarLerecel();
private static async Task GagarLerecel()
例如我这样写
await GagarLerecel(); private static async Task GagarLerecel()
{
Write("GagarLerecel 开始");
await Task.Delay(100);
Write("GagarLerecel 完成");
}
输出就是按照顺序输出
GagarLerecel 开始
GagarLerecel 完成
如果我修改一下代码,创建一个新的函数 CoujafuDarso
里面的代码和上面函数相同
private static async Task CoujafuDarso()
{
Write("CoujafuDarso开始");
await Task.Delay(100);
Write("CoujafuDarso结束");
}
但是不在调用 CoujafuDarso
使用 await ,而是使用一个变量
var aa = CoujafuDarso();
Write("其他代码");
await aa;
就是这样的代码,我的小伙伴说,这样写不清真,实际上这样写也是清真的代码。在调用 CoujafuDarso
会在代码到第一个 await
函数就返回,于是先执行了CoujafuDarso开始
,然后函数返回,执行Write("其他代码")
,在最后await aa
才等待函数把所有代码执行完成。所以可以看到下面输出
CoujafuDarso开始
其他代码
CoujafuDarso结束
但是不加 await 的呢?也就是函数一直都没有等待,我再写一个函数BotujawWorpay
private static async Task BotujawWorpay()
{
Write("BotujawWorpay开始");
await Task.Delay(100);
Write("BotujawWorpay结束");
}
调用的时候没有等待
BotujawWorpay();
Write("CesearJemmeme");
这时会在输出CesearJemmeme
之后,某个时间继续执行函数
BotujawWorpay开始
CesearJemmeme
BotujawWorpay结束
这样和使用 void 函数有什么区别?
在执行的函数遇到第一个 await
就会返回,这样就可以继续执行函数下面的代码
输出下面代码
德熙逗比代码
BarpooseewhowGelpousacall 代码1 线程1
德熙逗比状态开始
BarpooseewhowGelpousacall 代码2 线程5
BarpooseewhowGelpousacall 代码3 线程4
BarpooseewhowGelpousacall 完成 线程5
多线程
不是所有的 await 都会开多线程,如下面的代码
static void Main(string[] args)
{
Write("开始");
Write("线程" + Thread.CurrentThread.ManagedThreadId); CeaXisci();
Task.Run(async () =>
{
await Task.Delay(1000);
MouvaypuNasjo();
});
while (true)
{
Console.Read();
}
} private static async Task BarpooseewhowGelpousacall()
{
Write("BarpooseewhowGelpousacall 代码1 线程" + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(10);
Write("BarpooseewhowGelpousacall 代码2 线程" + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(10);
Write("BarpooseewhowGelpousacall 完成 线程" + Thread.CurrentThread.ManagedThreadId);
}
也就是在没有Task.Delay
分开的代码,只要使用了 await 那么就可以在同个线程运行,请看输出。在最后的BarpooseewhowGelpousacall 完成
和这个函数后面的代码都在同一个线程运行,而上面的代码,可能是在同个线程,也可能在不同的线程
开始
线程1
CeaXisci 开始 线程1
BarpooseewhowGelpousacall 代码1 线程1
BarpooseewhowGelpousacall 代码2 线程5
BarpooseewhowGelpousacall 完成 线程4
CeaXisci 开始 完成4
相关博客
2019-8-31-C#-await-高级用法的更多相关文章
- C# await 高级用法
原文:C# await 高级用法 本文告诉大家 await 的高级用法,包括底层原理. 昨天看到太子写了一段代码,我开始觉得他修改了编译器,要不然下面的代码怎么可以编译通过 await "林 ...
- SQL[连载3]sql的一些高级用法
SQL[连载3]sql的一些高级用法 SQL 高级教程 SQL SELECT TOP SQL SELECT TOP 子句 SELECT TOP 子句用于规定要返回的记录的数目. SELECT TOP ...
- nmap命令-----高级用法
探测主机存活常用方式 (1)-sP :进行ping扫描 打印出对ping扫描做出响应的主机,不做进一步测试(如端口扫描或者操作系统探测): 下面去扫描10.0.3.0/24这个网段的的主机 nmap ...
- Git log高级用法
格式化Log输出 首先,这篇文章会展示几种git log格式化输出的例子.大多数例子只是通过标记向git log请求或多或少的信息. 如果你不喜欢默认的git log格式,你可以用git config ...
- [tcpreplay] tcpreplay高级用法--使用tcpreplay-edit进行循环动态发包
tcpreplay-edit提供了可对包进行修改的高级用法: --unique-ip Modify IP addresses each loop iteration to generate uniqu ...
- git log 高级用法
转自:https://github.com/geeeeeeeeek/git-recipes/wiki/5.3-Git-log%E9%AB%98%E7%BA%A7%E7%94%A8%E6%B3%95 内 ...
- 爬虫—Requests高级用法
Requests高级用法 1.文件上传 我们知道requests可以模拟提交一些数据.假如有的网站需要上传文件,我们也可以用requests来实现. import requests files = { ...
- ConxtMenu高级用法
##背景我们经常在列表的页面中,点击列表中的行,一般进入详情页面,长按列表中一行,会弹出一个菜单,包含了对某一行的操作(编辑.删除等等),也知道通常的用法: 0x01. 在Activity中注册需要上 ...
- Python彩蛋、字典、列表高级用法、元类、混入、迭代器、生成器、生成式、git
一.类与类的关系 关注公众号"轻松学编程"了解更多. is-a 继承 继承是指一个类(称为子类.子接口)继承另外一个类(称为父类.父接口)的功能, 并可以增加它自己的新功能的能力. ...
- Fiddler高级用法
Fiddler高级用法 1. 简单用法 Fiddler作为一个基于http协议的抓包工具,一直在业界有广泛使用.很多测试或者前端在使用Fiddler时,仅仅用于查看前端和服务端之间的请求信息.包括我作 ...
随机推荐
- 【JZOJ4788】【NOIP2016提高A组模拟9.17】序列
题目描述 输入 输出 样例输入 1 5 2 1 3 0 3 2 2 0 1 0 样例输出 1 数据范围 解法 考虑没有模的情况,问题就仅仅只是简单的差分问题(广告铺设): 设r[i]是第i位需要加的次 ...
- 外贸电子商务网站之Prestashop 安装后台中文语言包
1.先进入到后台,我们进入Localization-> Localization2, 在下面的国家列表中,我们选择china ,导入即可. 3.进入Localization-> Trans ...
- 使用Velero Restic快速完成云原生应用迁移至ACK集群
本文记录使用Velero Restic快速完成云原生应用迁移至ACK集群的实践过程. 0. 实践步骤概览 (1)创建GKE集群(或自建Kubernetes集群)(2)在GKE集群上部署示例应用Jenk ...
- @spoj - ADAMOLD@ Ada and Mold
目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一个长度为 N 的序列 A,将其划分成 K + 1 段,划分 ...
- chrome://inspect调试html页面空白,DOM无法加载的解决方案
chrome://inspect调试html页面空白,DOM无法加载的解决方案 先描述一下问题 有一段时间没碰huilder hybird app 开发了,今天调试的时候 chrome://inspe ...
- python系列之(3)爬取豆瓣图书数据
上次介绍了beautifulsoup的使用,那就来进行运用下吧.本篇将主要介绍通过爬取豆瓣图书的信息,存储到sqlite数据库进行分析. 1.sqlite SQLite是一个进程内的库,实现了自给自足 ...
- hdu5441 并查集 长春网赛
对于每次询问的大的值,都是从小的值开始的,那就从小到大处理,省去很多时间,并且秩序遍历一遍m; 这题cin容易超时,scanf明显快很多很多.G++又比C++快; //这代码scanf400+,cin ...
- java8 各种时间转换方法
java8 各种时间转换方法 本来按照常理日期时间是一个很简单的东西,只需要根据一个时间戳就可以算出当前的时间了.但这其实只是初级的想法,是因为你的项目还没有到跨时区部署的程度,一旦你的项目要部署到其 ...
- SDUT-3361_迷宫探索
数据结构实验之图论四:迷宫探索 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 有一个地下迷宫,它的通道都是直的,而通道 ...
- css的两栏布局
经典的实现左边固定宽度,右边宽度自适应的几种方法 利用float和margin-left属性(margin-left的值可以稍稍大于或者等于.left的宽度) .left{ width: 30px; ...