您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

一文看懂"async"和“await”关键词是如何简化了C#中多线程的开发过程

时间:2021-12-01 10:05:32  来源:  作者:zls315

一文看懂"async"和“await”关键词是如何简化了C#中多线程的开发过程

当我们使用需要长时间运行的方法(即,用于读取大文件或从网络下载大量资源)时,在同步的应用程序中,应用程序本身将停止运行,直到活动完成。在这些情况下,异步编程非常有用:它使我们能够并行执行不同任务,并在需要时等待其完成。

这种方法有许多不同的模型类型:APM(异步编程模型),基于事件(异步模型EAP),以及TAP,基于任务的(异步模型任务)。让我们看看如何使用关键字async和await在C#中实现第三个方法。

编写异步代码的主要问题之一是可维护性:实际上,许多人普遍认为这种编程方法会使代码复杂化。幸运的是,C#5引入了一种简化的方法,在该方法中,编译器运行由开发人员先前完成的艰巨任务,并且应用程序保留类似于同步代码的逻辑结构。

让我们举个例子。假设我们有一个.NET Core项目,我们应该在其中管理三个实体:Area,Company和Resource。

public class Area{ public int Id { get; set; }
 [Required] [StringLength(255)] public string Name { get; set; }}
public class Company{ public int Id { get; set; }
 [Required] [StringLength(255)] public string Name { get; set; }}
public class Resource{ public int Id { get; set; }
 [Required] [StringLength(255)] public string Name { get; set; }}

现在假设我们应该使用Entity Framework Core将这些实体的值保存在数据库中。其DbContext是:

public class AppDbContext : DbContext{ public DbSet<Area> Areas { get; set; } public DbSet<Company> Companies { get; set; } public DbSet<Resource> Resources { get; set; } public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}
 override protected void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Area> .HasData( new Area { Id = 1, Name = "Area1"}, new Area { Id = 2, Name = "Area2"}, new Area { Id = 3, Name = "Area3"}, new Area { Id = 4, Name = "Area4"}, new Area { Id = 5, Name = "Area5"}); modelBuilder.Entity<Company> .HasData( new Area { Id = 1, Name = "Company1"}, new Area { Id = 2, Name = "Company2"}, new Area { Id = 3, Name = "Company3"}, new Area { Id = 4, Name = "Company4"}, new Area { Id = 5, Name = "Company5"}); modelBuilder.Entity<Resource>.HasData( new Area { Id = 1, Name = "Resource1"}, new Area { Id = 2, Name = "Resource2"}, new Area { Id = 3, Name = "Resource3"}, new Area { Id = 4, Name = "Resource4"}, new Area { Id = 5, Name = "Resource5"}); }}

从代码中可以看到,我们插入了一些示例数据进行处理。现在假设我们要使用Controller API公开这些数据,既单独(针对每个实体),又使用将它们全部联接在一起的方法,并通过一次调用返回它们。

使用同步方法,Controller API 将是:

[ApiController][Route("[controller]")]public class DataController : ControllerBase{ private readonly AppDbContext db = ;
 public DataController(AppDbContext db) { this.db = db; }
 public IActionResult Get { var areas = this.GetAreas; var companies = this.GetCompanies; var resources = this.GetResources; return Ok(new { areas = areas, companies = companies, resources = resources }); }
 [Route("areas")] public Area GetAreas  { return this.db.Areas.ToArray; }
 [Route("companies")] public Company GetCompanies  { return this.db.Companies.ToArray; }
 [Route("resources")] public Resource GetResources  { return this.db.Resources.ToArray; }}

Get方法在其中调用返回单个结果的三个方法,并等待每个方法的执行完成后再传递到下一个结果。这三种方法互不相关,因此您无需等待其中一种方法的执行即可调用另一种方法。然后,您可以创建三个独立的任务以并行执行。

第一种方法可以基于该方法Task.Run作业运行在线程池之上,并返回一个任务对象,它代表了这项工作。这样,方法可以在线程池的不同线程上同时运行:

public IActionResult Get{ var areas = Task.Run( = > this.GetAreas); var companies = Task.Run( = > this.GetCompanies); var resources = Task.Run( = > this.GetResources);  Task.WhenAll(areas, companies, resources); return Ok(new { areas = areas.Result, companies = companies.Result, resources = resources.Result });}

TaskResult属性包含详细说明的结果。方法WhenAll允许暂停当前线程执行,直到所有Task完成。运行代码,我们可以注意到一个有趣的事情:调用中断,并启动以下异常:

AggregateException:发生一个或多个错误。(在上一个操作完成之前,第二个操作在此上下文上开始。这通常是由使用相同DbContext实例的不同线程引起的。有关如何避免DbContext线程问题的更多信息,请参见
https://go.microsoft.com/fwlink/?linkid=2097913。[1]

此错误消息告诉我们,方法在不同的线程上同时执行,但是由于它们使用与DbContext相同的实例来连接数据库, 因此引发了异常,DbContext类无法确保线程安全的功能:我们可以轻松地绕过此问题,避免了.NET Core 的依赖注入引擎创建单个实例,而我们为每种方法创建了单独的实例。作为示例,让我们看看方法GetAreas会如何变化:

public class DataController : ControllerBase{ private readonly DbContextOptionsBuilder <AppDbContext> optionsBuilder = ;
 public DataController(IConfiguration configuration) { this.optionsBuilder = new DbContextOptionsBuilder <AppDbContext>  .UseSqlite(configuration.GetConnectionString("DefaultConnection")); }
 [Route("areas")] public Area GetAreas  { using(var db = new AppDbContext(this.optionsBuilder.Options)) { return db.Areas.ToArray; } }}

好吧,现在可以了。我们应该注意,EFCore提供了一些方法,例如,与方法ToArrayAsync一样,使用相同的DbContext进行异步调用,该方法从IQueryable 创建一个数组,该数组 异步枚举它。此方法返回Task ,它是表示异步操作的活动。

这样,我们不再需要使用Task.Run:

public IActionResult Get{ var areas = this.GetAreas; var companies = this.GetCompanies; var resources = this.GetResources; Task.WhenAll(areas, companies, resources); return Ok(new { areas = areas.Result, companies = companies.Result, resources = resources.Result });}
[Route("areas")]public Task<Area> GetAreas { return db.Areas.ToArrayAsync;}

无论如何,Microsoft不能保证这些异步方法在每种情况下都能工作,因为DbContext尚未设计为线程安全的。您可以查询此链接以获取更多信息:https :
//docs.microsoft.com/zh-cn/ef/core/querying/async

使用Entity Framework Core时,最佳实践是在启动另一个异步操作之前,为每个异步操作都拥有一个DbContext或等待每个异步操作完成。当我们必须进行异步调用并返回结果时,这种最佳做法是可以的。

但是,如果我们想在返回结果之前对结果进行一些操作,会发生什么?如果我们想向列表中添加元素怎么办?我们应该等待结果,添加元素,然后返回修改后的列表:

[Route("companies")]public Task<Company> GetCompanies { using (var db = new AppDbContext(this.optionsBuilder.Options)) { var data = this.db.Companies.ToListAsync.Result; data.Insert(0, new Company { Id = 0, Name = "-"}); return data.ToArray; }}

不幸的是,该代码无法编译,因为data.ToArray返回的是数组而不是Task。实际上,这里我们需要三个线程:主调用方(Get),数据库查询(
this.db.Companies.ToListAsync)和一个线程,该线程将一个值添加到列表中。我们有三种方法可以做到这一点:让我们用三种单一方法来查看它们。我们已经看到的第一个,可以使用Task.Run方法:

[Route("companies")]public Task<Company> GetCompanies{ return Task.Run( => { using (var db = new AppDbContext(this.optionsBuilder.Options)) { var data = db.Companies.ToList; data.Insert(0, new Company { Id = 0, Name = "-" }); return data.ToArray; } });}

作为替代方案,我们可以使用方法ContinueWith,该方法可以应用于任务,并且可以在上一个方法完成后立即指定要运行的新任务:

[Route("resources")]public Task <Resource> GetResources{ using (var db = new AppDbContext(this.optionsBuilder.Options)) { return db.Resources.ToListAsync .ContinueWith(dataTask = > { var data = dataTask.Result; dataTask.Result.Insert(0, new Resource { Id = 0, Name = "-" }); return data.ToArray; }); }}

我们可以让编译器执行“垃圾代码”,并使用关键字asyncawait,这可以为我们创建Task:

[Route("areas")]public async Task <Area> GetAreas{ using (var db = new AppDbContext(this.optionsBuilder.Options)) { var data = await db.Areas.ToListAsync; data.Insert(0, new Area { Id = 0, Name = "-" }); return data.ToArray; }}

正如您在最后一种方法中看到的那样,代码更加简单,并且向我们隐藏了Task的创建,从而使我们可以异步返回。让我们想象一下一个场景,其中调用不止一个,并且这种方法如何使一切变得更加线性。

重构的作用是方法GetAreas已成为异步操作。这个事实意味着,当不同的请求到达此API时,分配给该请求的线程池的线程将被释放以供其他请求使用,直到DbContext终止数据提取为止。

我希望我能引起您足够的兴趣来深入分析该论点。在许多情况下,使用async和await非常方便,并且除了使代码更加简洁和线性外,还可以提高一般应用程序的性能。

示例代码见:

https://github.com/fvastarella/Programmazione-asincrona-con-async-await

 

References

[1]https:https://docs.microsoft.com/en-us/ef/core/querying/async

[2]//docs.microsoft.com/zh-cn/ef/core/querying/async:https://docs.microsoft.com/en-us/ef/core/querying/async



Tags:async   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
一文看懂"async"和“await”关键词是如何简化了C#中多线程的开发过程当我们使用需要长时间运行的方法(即,用于读取大文件或从网络下载大量资源)时,在同步的应用程序中,应用程序本...【详细内容】
2021-12-01  Tags: async  点击:(23)  评论:(0)  加入收藏
1、前言async函数,也就是我们常说的async/await,是在ES2017(ES8)引入的新特性,主要目的是为了简化使用基于Promise的API时所需的语法。async和await关键字让我们可以用一种更简...【详细内容】
2021-09-17  Tags: async  点击:(61)  评论:(0)  加入收藏
一般在开发中,查询网络 API 操作时往往是比较耗时的,这意味着可能需要一段时间的等待才能获得响应。因此,为了避免程序在请求时无响应的情况,异步编程就成为了开发人员的一项基...【详细内容】
2021-03-31  Tags: async  点击:(260)  评论:(0)  加入收藏
大家好,前几篇文章我们一起学习了「JavaScript基础」Promise使用指南, 明白了ES6增加的新特性&mdash;&mdash;Promise让我们能够更加优雅的书写回调函数,清楚了Promise有哪些...【详细内容】
2021-03-30  Tags: async  点击:(267)  评论:(0)  加入收藏
译者:littlebrain4solving来源: https://blog.csdn.net/littlebrain4solving/概要说明在此篇文章中,我们根据使用@Async注解进行探索Spring对异步执行的支持。简单的把@Async注...【详细内容】
2021-03-05  Tags: async  点击:(378)  评论:(0)  加入收藏
微信小程序中有大量接口是异步调用,比如 wx.login() 、 wx.request() 、 wx.getUserInfo() 等,都是使用一个对象作为参数,并定义了 success() 、 fail() 和 complete() 作为异...【详细内容】
2020-03-11  Tags: async  点击:(93)  评论:(0)  加入收藏
很多语言使用async和await作为语法,将函数变成async,然后在代码的某个位置,它会进行等待,直到任务处理完成返回。在flutter中也是这样的基本功能,但是需要特别指出的是这种语法糖只是feature和stream的封装,可以让你更清晰...【详细内容】
2019-11-20  Tags: async  点击:(93)  评论:(0)  加入收藏
自从Async 和Await 出现后,大幅简化JavaScript 同步和非同步(异步)的复杂纠葛,这篇文章将会分享我自己理解的历程,实站await 等待、连续输入文字、fetch 和回调应用,让这些过去需...【详细内容】
2019-08-30  Tags: async  点击:(247)  评论:(0)  加入收藏
▌简易百科推荐
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(1)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(9)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(19)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(23)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(24)  评论:(0)  加入收藏
一个项目的大部分API,测试用例在参数和参数值等信息会有很多相似的地方。我们可以复制API,复制用例来快速生成,然后做细微调整既可以满足我们的测试需求1.复制API:在菜单发布单...【详细内容】
2021-12-14  AutoMeter    Tags:AutoMeter   点击:(20)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条