async/await异步操作,是C#中非常惊艳的“语法糖”,让异步编程变得优美且傻瓜化到了不可思议的程度。就连JavaScript都借鉴了async/await语法,让回调泛滥的JavaScript代码变得很优美。

我之前录制的.NET视频教程已经把async/await等基础知识介绍了,这篇文章不再介绍那些基础知识,如果有对它们还不了解的朋友,请到我的B站、头条、油管等平台搜索“杨中科 .net 教程”查看。

本篇文章只对在之前的视频教程中没有提到的几点做讲解。

用法1、控制并行执行的任务数量

在项目开发的时候,有时候有很多任务需要异步执行,但是为了避免同时执行的异步任务太多,反而降低性能,因此通常需要限制并行执行的任务的数量。比如爬虫并行从网上抓取内容的时候,就要根据情况限制最大执行的线程的数量。

在没有async/await的年代,需要使用信号量等机制来进行线程间通讯来协调各个线程的执行,需要开发者对于多线程的技术细节非常了解。而使用async/await之后,这一切就可以变得非常傻瓜化了。

比如下面的代码用来首先从words.txt这个每行一个英文单词的字典中,逐个读取单词,然后调用一个API接口来获得单词的“音标、中文含义、例句”等详细信息。为了加快处理速度,需要采用异步编程来实现多任务同时下载,但是又要限制同时执行的任务的数量(假设为5个)。实现代码如下:

class Program
{static async Task Main(string[] args){ServiceCollectionservices = new ServiceCollection();services.AddHttpClient();services.AddScoped<WordProcessor>();using(var sp = services.BuildServiceProvider()){var wp = sp.GetRequiredService<WordProcessor>();string[]words = await File.ReadAllLinesAsync("d:/temp/words.txt");List<Task>tasks = new List<Task>();foreach(var word in words){tasks.Add(wp.ProcessAsync(word));if(tasks.Count==5){//waitwhen five tasks are readyawai tTask.WhenAll(tasks);tasks.Clear();}}//waitthe remnant which are less than five.await Task.WhenAll(tasks);}Console.WriteLine("done!");}
}class WordProcessor
{private IHttpClientFactory httpClientFactory;public WordProcessor(IHttpClientFactory httpClientFactory){this.httpClientFactory= httpClientFactory;}publicasync Task ProcessAsync(string word){Console.WriteLine(word);var httpClient = this.httpClientFactory.CreateClient();string json = await httpClient.GetStringAsync("http://dict.e.opac.vip/dict.php?sw="+ Uri.EscapeDataString(word));await File.WriteAllTextAsync("d:/temp/words/" + word + ".txt",json);}
}

核心代码就是下面这一段:

List<Task> tasks = newList<Task>();
foreach(var word in words)
{tasks.Add(wp.ProcessAsync(word));if(tasks.Count==5){//waitwhen five tasks are readyawait Task.WhenAll(tasks);tasks.Clear();}
}

这里遍历所有单词,抓取单词并且保存到磁盘的Process方法的返回值Task没有使用await关键字进行修饰,而是把返回的Task对象保存到list中,由于没有使用await进行等待,因此不用等一个任务执行完成,就可以把下一个任务加入list。当list中的任务满五个的时候,就调用await Task.WhenAll(tasks);等待这五个任务执行完成后,再处理下一组(5个)。循环之外的await Task.WhenAll(tasks);的是用来处理最后一组不足5个任务的情况。

用法2、在BackgroundService等异步执行的代码中进行DI注入

依赖注入(DI)的时候,注入的对象都是有生命周期的。比如使用services.AddDbContext<TestDbContext>(...);这种方式注入EF Core中的DbContext的时候,TestDbContext的生命周期就是Scope。在普通的MVC的Controller中可以直接注入TestDbContext,但是在BackgroundService中是不能直接注入TestDbContext的。这时候,可以注入IServiceScopeFactory对象,然后在使用到TestDbContext对象的时候再调用IServiceScopeFactory的CreateScope()方法来生成一个IServiceScope,并且使用IServiceScope的ServiceProvider来手动解析获取TestDbContext对象。

代码如下:

public classTestBgService:BackgroundService
{private readonly IServiceScopeFactory scopeFactory;public TestBgService(IServiceScopeFactory scopeFactory){this.scopeFactory= scopeFactory;}protected override Task ExecuteAsync(CancellationToken stoppingToken){using(var scope = scopeFactory.CreateScope()){var sp = scope.ServiceProvider;var dbCtx = sp.GetRequiredService<TestDbContext>();foreach(var b in dbCtx.Books){Console.WriteLine(b.Title);}}               return Task.CompletedTask;}
}

用法3、异步方法可以不await

我在做youzack背单词的时候,有一个查询单词的功能。为了提升客户端的响应速度,我把每个单词的明细信息都按照“每个单词一个json文件”的形式,把单词的详细信息保存到文件服务器,相当于做了一个“静态化”。因此客户端在查询单词的时候,先到文件服务器中查找一下是否有对应的静态文件,如果有的话,就直接加载静态文件。如果在文件服务器不存在的话,再调用API接口的方法去查询,API接口从数据库中查询到单词后,不仅会把单词的详细信息返回给客户端,而且还会把单词的详细信息再上传到文件服务器。这样以后客户端再查询这个单词,就可以直接从文件服务器查询了。

因此API接口中“把从数据库中查询到的单词的详细信息上传到文件服务器”这个操作对于接口的请求者来讲没什么意义,而且会降低接口的响应速度,因此我就把“上传到文件服务器”这个操作写到了异步方法中,并且没有通过await来等待。

伪代码如下:

public async Task<WordDetail>FindWord(string word)
{var detail = await db.FindWordInDBAsync(word);//从数据库里查询_=storage.UploadAsync($”{word}.json”,detail.ToJsonString());//上传到文件服务器,但是不等待returnd etail;
}

在上面的UploadAsync调用中没有await调用等待,因此只要从数据库中查询出来,就把detail返回给请求者了,留下UploadAsync在异步线程中慢慢执行。

前面加的“_=”是消除对于不await异步方法造成编译器警告。

用法4、异步代码中Sleep的坑

 

在编写代码的时候,有时候我们需要“暂停一段时间,再继续执行代码”。比如调用一个Http接口,如果调用失败,则需要等待2秒钟再重试。

在异步方法中,如果需要“暂停一段时间”,那么请使用Task.Delay(),而不是Thread.Sleep(),因为Thread.Sleep()会阻塞主线程,就达不到“使用异步提升系统并发能力”的目的了。

如下代码是错误的:

public async Task<IActionResult> TestSleep()
{await System.IO.File.ReadAllTextAsync("d:/temp/words.txt");Console.WriteLine("firstdone");Thread.Sleep(2000);awaitSystem.IO.File.ReadAllTextAsync("d:/temp/words.txt");Console.WriteLine("seconddone");returnContent("xxxxxx");
}

上面的代码是能够正确的编译执行的,但是会大大降低系统的并发处理能力。因此要用Task.Delay()代替Thread.Sleep()。如下是正确的:

public async Task<IActionResult> TestSleep()
{awaitSystem.IO.File.ReadAllTextAsync("d:/temp/words.txt");Console.WriteLine("firstdone");awaitTask.Delay(2000);//!!!awaitSystem.IO.File.ReadAllTextAsync("d:/temp/words.txt");Console.WriteLine("seconddone");returnContent("xxxxxx");
}

用法5、yield如何用到异步方法中

yield由于可以实现“产生一个数据就让IEnumerable的使用者处理一个数据”,从而实现数据处理的“流水线化”,提升数据处理的速度。

但是,由于yield和async都是编译器提供的语法糖,编译器都会把它们修饰的方法编译为一个使用了状态机的类。因此两个语法糖碰到一起,编译器就迷惑了,因此不能直接在async修饰的异步方法中使用yield返回数据。

因此下面的代码是错误的:

static async IEnumerable<int>ReadCC()
{foreach(string line in await File.ReadAllLinesAsync("d:/temp/words.txt")){yieldreturn line.Length;}
}

只要把IEnumerable改成IAsyncEnumerable就可以了,如下是正确的:

static async IAsyncEnumerable<int>ReadCC()
{foreach(stringline in await File.ReadAllLinesAsync("d:/temp/words.txt")){yieldreturn line.Length;}
}

但是调用同时使用了async和yield的代码,不能使用普通的foreach+await,如下是错误的:

foreach (int i in await ReadCC())
{Console.WriteLine(i);
}

需要把await关键词移动到在foreach之前,如下是正确的:

await foreach(int i in ReadCC())
{Console.WriteLine(i);
}

编译器是微软写的,不知道为什么不支持foreach (int i in awaitReadCC())这样的写法,可能是由于为了兼容之前的C#语法规范不得已而为之吧。

.NET 异步,你也许不知道的5种用法相关推荐

  1. 你也许不知道的Vuejs - 前言

    by yugasun from yugasun.com/post/you-ma- 本文可全文转载,但需要保留原作者和出处. 写在最前 <你也许不知道的Vuejs>系列文章是本人在过去一年多 ...

  2. python装饰器有几种_Python装饰器使用你可能不知道的几种姿势

    前言 在Python中,装饰器是一种十分强大并且好用的语法,一些重复的代码使用装饰器语法的话能够使代码更容易理解及阅读. 因此在这里简单总结了一下Python中装饰器的几种用法以及需要注意的事情. 一 ...

  3. 酷炫时钟_您不知道的11种酷炫形状

    酷炫时钟 Whether it's in nature, architecture or the products we use, cool shapes are everywhere around ...

  4. 你可能不知道的5种 CSS 和 JS 的交互方式

    翻译人员: 铁锚 翻译日期: 2014年01月22日 原文日期: 2014年01月20日 原文链接:  5 Ways that CSS and JavaScript Interact That You ...

  5. 你也许不知道的Vuejs - 使用ES6快乐的玩耍

    by yugasun from yugasun.com/post/you-ma- 本文可全文转载,但需要保留原作者和出处. 上一篇中我们已经学会使用 babel 将 ES6 转化为 ES5 了,并且展 ...

  6. vue 代码快捷键_你可能不知道的19种运行JavaScript代码工具

    前端日常开发中,我们使用喜爱的 IDE 调试 JavaScript 代码,比如我喜欢的代码编辑器有两个,Sublime Text 3 和 VS Code,前几年还使用过 Atom,偶尔我们会遇到临时需 ...

  7. 【转载】35 个你也许不知道的 Google 开源项目

    Google是支持开源运动的最大公司之一,它们现在总共发布有超过500个的开源项目(大部分都是利用它们的API来完成),本文将列举一些有趣的开源项目,其中很可能有不少你不知道的哦. 文本文件处理: G ...

  8. 数据分析师不能不知道的5种数据分析方法,解决90%分析难题!

    网上介绍了那么那么多的数据分析方法,但不同的数据分析方法使用场景不同,A常用的B不一定常用. 所以这篇只介绍5种基于逻辑层面的,几乎人人都会用的数据分析方法. 先来分享一下数据分析6大步骤: 按照这6 ...

  9. vuejs知乎_你也许不知道的Vuejs - 深入浅出响应式系统

    虽然说是Vuejs实践,但是有些重要的理论还是必不可少的,本文将简单的带你了解 Vuejs的响应式原理.Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 Javascript ...

最新文章

  1. 进程间通信-Queue
  2. Android setTag()/getTag()
  3. python 安装lxml
  4. 018_SpringBoot异常处理方式-ExceptionHandle注解处理异常
  5. 牛客练习赛46 A 华华教奕奕写几何 (简单数学)
  6. web前端工程师热门岗位技能要求前瞻
  7. virtualbox禁用硬件虚拟化_Mac版Virtualbox6.1开启嵌套虚拟化
  8. php连接mysql的字符集问题_关于php7 设计链接mysqlutf8mb4字符集的问题
  9. 哈工大2022形式语言与自动机期末
  10. Android Contacts 联系人源码分析
  11. 敏感词过滤的算法原理之 Aho-Corasick 算法
  12. Learn Git Branching 笔记
  13. 不积跬步 无以至千里
  14. 一寸照片电子版怎么弄?电子证件照制作方法
  15. Opencv-python形态学滤波操作
  16. 期刊论文发表什么是省级刊物
  17. 1 -【第十一届】蓝桥杯物联网试题(模拟题)
  18. poj1737 Connected Graph(计数,组合数学,递推,高精)
  19. 纯JavaScript 实现JSON数据导出到Excel(支持多个Sheet页)
  20. sap 界面创建凭证_查看会计凭证

热门文章

  1. python并行for循环_Python并行执行for循环
  2. 排序算法-C++实现
  3. spring boot拦截器中获取request post请求中的参数(转)
  4. python实现二叉树和它的七种遍历
  5. bzoj3122 [Sdoi2013]随机数生成器(bsgs+扩欧+数列)
  6. 第九十五节,移动流体布局和响应式布局总结
  7. hdu 4530(数学)
  8. 趣谈unicode,ansi,utf-8,unicode big endian这些编码有什么区别
  9. t30智能插座怎么设置_如何设置ConnectSense智能插座
  10. word 替换 增加引号_如何在Word 2013文档中替换部分(不是全部)智能引号