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

一文看懂"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 = null;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 = null;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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308621.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Nginx]nginx常用的命令

nginx常用的命令 使用nginx操作命令前提条件:必须进入nginx的目录 cd /www/local/webserver/nginx/sbin查看nginx的版本号 ./nginx -v启动nginx ./nginx关闭nginx ./nginx -s stop重新加载nginx ./nginx -s reload

pc 图片预览放大 端vue_安利一款简单好用的Vue图片预览插件

在项目中因为要经常用到图片预览效果&#xff0c;自己写的话麻烦死啦(懒)vue-photo-preview一个基于 photoswipe 的 vue 图片预览插件&#xff0c;支持移动端和PC端&#xff0c;支持各种手势操作&#xff0c;放大缩小&#xff0c;体验流畅。gitHub: https://github.com/8263277…

[Nginx]nginx的配置文件

nginx配置文件 nginx配置文件位置 nginx配置文件由三部分组成 第一部分 全局块 从配置文件开始到 events 块之间的内容&#xff0c;主要会设置一些影响 nginx 服务器整体运行的配置指令&#xff0c;主要包括配置运行 Nginx 服务器的用户&#xff08;组&#xff09;、允许生成…

pe下找不到ssd硬盘_【进入pe系统后认不到硬盘解决方法】进入pe系统看不到硬盘_pe系统不认硬盘...

2014-01-07 11:11:42有个别朋友在使用一键U盘装系统工具安装系统&#xff0c;在进入pe装系统时发现找不到电脑硬盘了&#xff0c;接下来看看小编为大家带来的解决方法&#xff01;2017-03-01 17:01:06pe是装系统最常用到的预安装环境&#xff0c;只需通过启动盘制作工具就可以将…

Abp vNext 二进制大对象系统(BLOB)

一、简介ABP vNext 在 v 2.9.x 版本当中添加了 BLOB 系统&#xff0c;主要用于存储大型二进制文件。ABP 抽象了一套通用的 BLOB 体系&#xff0c;开发人员在存储或读取二进制文件时&#xff0c;可以忽略具体实现&#xff0c;直接使用IBlobContainer 或 IBlobContainer<T>…

[Nginx]location 指令说明

location 指令说明 该指令用于匹配 URL。 语法如下&#xff1a; 1、 &#xff1a;用于不含正则表达式的 uri 前&#xff0c;要求请求字符串与 uri 严格匹配&#xff0c;如果匹配 成功&#xff0c;就停止继续向下搜索并立即处理该请求。 2、~&#xff1a;用于表示 uri 包含正则…

卡尔曼_卡尔曼估计两步法

在上一篇文章中手把手推导了一遍卡尔曼增益&#xff0c;不熟悉的小伙伴可以看养生的控制人&#xff1a;卡尔曼增益推导​zhuanlan.zhihu.com这里再回顾一下重点。问题重述假设真实系统为其中 。我们对系统状态的估计&#xff08;数据融合&#xff09;为其中卡曼尔增益为我们可以…

基于GitBook框架搭建技术文档平台

源宝导读&#xff1a;为了向用户更好的传递ERP开放平台的价值与技术知识&#xff0c;我们基于GitBook框架搭建了一个文档中心站点&#xff0c;本文将介绍此站点的设计与实现过程。一、项目架构图因为文档会涉及到很多的产品线&#xff0c;所以目前主要是通过拉取各个产品线的文…

C++中有关queue常用函数的用法及其注意要项

11&#xff1a;C中有关queue常用函数的用法及其注意要项 #include<bits/stdc.h> using namespace std; int main(){queue <int> q;q.push() //在队尾插入一个元素q.pop() //删除队列第一个元素q.size() //返回队列中元素个数q.empty() //如果队列空则返回true…

[Nginx]nginx 配置实例-负载均衡

nginx 配置实例-负载均衡 1、实现效果 &#xff08;1&#xff09;浏览器地址栏输入地址 http://192.168.111.134/edu/a.html&#xff0c;负载均衡效果&#xff0c;平均分担到 8080和 8081 端口中 2、准备工作 &#xff08;1&#xff09;准备两台 tomcat 服务器&#xff0c;…

css3边框交替动画_用css3实现惊艳面试官的背景即背景动画(高级附源码)

我们传统的前端更多的是用javascript实现各种复杂动画&#xff0c;自从有了Css3 transition和animation以来,前端开发在动画这一块有了更高的自由度和格局,对动画的开发也越来越容易。这篇文章就让我们汇总一下使用Css3实现的各种特效。这篇文章参考《css揭秘》这本书&#xff…

用Blazor技术封装G2Plot实现Charts组件

Blazor是一个使用 .NET 生成交互式客户端 Web UI 的框架。目前社区刚起步&#xff0c;相关的组件并不多&#xff0c;有幸有一群爱好者正在努力建设社区&#xff0c;我作为社区一员也来贡献一些内容。这里我就分享分享我封装G2Plot后的Blazor组件ant-design-charts-blazor。ant-…

C++与C语言中有关数组中元素排序

C与C语言中有关数组中元素排序 C语言中 ​ #include<stdio.h> #define n 4 int main(){ int a[n]; int i,j,temp; for(i0;i<n;i) scanf("%d",&a[i]); for(i0;i<n-1;i){ for(j0;j<n-1-i;j) if(a[j]>a[j1]){ tempa[j]; a[j]a[j1]; a[j1]temp; }…

[Nginx]nginx配置实例_反向代理

nginx 配置实例-反向代理1 1、实现效果 &#xff08;1&#xff09;打开浏览器&#xff0c;在浏览器地址栏输入地址 www.123.com&#xff0c;跳转到 liunx 系统 tomcat 主页面中 2、准备工作 &#xff08;1&#xff09;在 liunx 系统安装 tomcat&#xff0c;使用默认端口 80…

lts安装 rust ubuntu_一起学Rust编程「1」:开发环境

引言Rust是近几年获得广泛关注和认可的一门系统级编程语言。它严苛的静态类型检查和独特的所有权系统&#xff0c;使得编译器能够尽可能的帮开发者在编译时就排除一些符合常见模式的bug。这也让很多人认为rust是一门更加“安全”的语言。专注数据安全技术的红小豆同学也非常看好…

使用 iPerf 测试 Azure VM 之间的网速

点击上方关注“汪宇杰博客” ^_^导语以往提到测网速&#xff0c;大家可能想到的都是用著名的 speedtest 等工具测试互联网连接速度。但实际上仅仅测试互联网连接速度并不可靠&#xff0c;在部分应用场景里网速还受到服务器之间的连接速度影响&#xff0c;因此清楚你的网络性能瓶…

[Nginx]nginx 配置实例-动静分离

nginx 配置实例-动静分离 1、什么是动静分离 Nginx 动静分离简单来说就是把动态跟静态请求分开&#xff0c;不能理解成只是单纯的把动态页面和静态页面物理分离。严格意义上说应该是动态请求跟静态请求分开&#xff0c;可以理解成使用 Nginx 处理静态页面&#xff0c;Tomcat 处…

7-1 修理牧场 (25 分)(最详解)(最容易理解的解题过程)

7-1 修理牧场 (25 分)&#xff08;最详解&#xff09;&#xff08;最容易理解的解题过程&#xff09; 农夫要修理牧场的一段栅栏&#xff0c;他测量了栅栏&#xff0c;发现需要N块木头&#xff0c;每块木头长度为整数L​i​​个长度单位&#xff0c;于是他购买了一条很长的、能…

收购最大K8s服务商,重回独立的SUSE又要和Red Hat拼混合云

7月8日&#xff0c;SUSE 宣布收购 Kubernetes 管理平台公司 Rancher Labs&#xff0c;交易预计在2020年10月底之前完成。有外媒称&#xff0c;收购价预估在6亿至7亿美元之间。 宣布要收购之后&#xff0c;SUSE 的介绍前缀中又多了个关键词——Kubernetes&#xff0c;变成企业级…

post获取重定向的链接 python_【转载】python面试基础知识(四) 网络部分

最近&#xff0c;小编在整理python面试基础知识&#xff0c;看了很多博客、文章和咨询了一些大厂公司大牛。了解到&#xff0c;在python面试的时候&#xff0c;不仅要求你有项目经验&#xff0c;还要考试代码呢&#xff01;今天&#xff0c;小编和大家分享一下python面试基础知…