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. 带你100% 地了解 Redis 6.0 的客户端缓存
  2. 徐科:做IC不外乎PPA,但需要成百上千的专家合作 投入数千万
  3. git查看一个文件的历史记录
  4. XenApp Farm:修改密码、退域、更改Farm
  5. 【电子电路】上拉电阻与下拉电阻有什么作用
  6. 在一周之内,快速看完整部教材,列出你不懂的5-10个问题。
  7. Qt-捕获Windows消息
  8. PHP爬虫音乐,PHPCrawl爬虫库实现抓取酷狗歌单
  9. jquery-等待加载-显示隐藏-遍历
  10. Android笔记 - Android studio如何添加arr库
  11. 赵明晒荣耀20青春版三色真机图:哪款会是你的菜?
  12. 从15000个Python开源项目中精选TOP30,GitHub平均star为3707,赶紧收藏!
  13. python open函数encoding_关于python内open函数encoding编码问题
  14. 中科大博士写外挂被抓:涉案总牟利 300 多万,每月分得 4~6k
  15. 十天学会php之第八天
  16. MySQL笔记4——SQL去重/笛卡尔积现象
  17. 【6】python生成数据曲线平滑处理——(Savitzky-Golay 滤波器、convolve滑动平均滤波)方法介绍,推荐玩强化学习的小伙伴收藏
  18. 【波导】——理解群速度和相速度
  19. 电脑上怎么截图按什么键?电脑截图的快捷键是什么?
  20. Pigeon中的流量限制

热门文章

  1. 台电u盘量产工具_简单几步,让U盘起死回生
  2. ajax 页面无刷新
  3. 【编程大系】Java资源汇总
  4. 一位面试者提到直接调用vuex中mutations方法
  5. 【转载】负数的二进制
  6. 62、滑动窗口的最大值
  7. C#深入.NET平台的软件系统分层开发
  8. 面向对象之迪米特法则
  9. Linux内核升级,从2.6.18升级到3.2.14
  10. java mina unix client