目录

介绍

您对异步编程了解什么?

那么,什么是异步编程?

我们什么时候应该使用它?

任务、线程、计划、上下文

到底是怎么回事?

Asnyc编码模式

命名约定

异步任务模式

任务模式

事件模式

阻塞与死锁

推荐建议

现实世界中的一些例子

数据服务

控制器(业务层)服务

UI代码

我的知识的有用资源和来源


介绍

本文提供了对DotNetCore中异步编程以及在Blazor中实现异步编程的见解。我并没有声称自己是专家:这是我最近的经验和知识积累的总结。有一些原始内容,但是我写的大部分内容都是取自其他作者的作品。底部有一个指向我发现有用的文章,博客和其他材料的链接列表,这些链接是我撰写本文时所挖掘的。富有建设性的批评和错误修复。

现代程序依赖于远程运行的数据库和服务。他们需要为延迟做好准备。了解异步编程并开发使用异步编程的应用程序正迅速成为一项关键技能。

您对异步编程了解什么?

询问程序员是否了解异步编程。他们可能会点头,然后停顿一下。在开始认真使用异步编程之前,我是一个人。我很快就痛苦地意识到这种知识的深度。是的,可以肯定,我对此一无所知,可以广泛地解释它。但是实际上编写的是结构良好且行为良好的代码吗?随之而来的是谦卑的教训。

那么,什么是异步编程?

简而言之,异步编程使我们可以执行多项任务,例如在与乘客交谈时驾驶汽车。Microsoft Docs网站上有一个很好的解释,描述了如何使并行任务变热或顺序执行。

我们什么时候应该使用它?

与单一顺序流程相比,异步流程的三种主要情况具有明显的优势:

  1. 处理器密集型操作——例如复杂的数学计算
  2. I/0操作——将任务卸载到同一台计算机上的子系统或在远程计算机上运行
  3. 改善的用户界面体验

在处理器密集型操作中,您需要多个处理器或内核。将大多数处理交给这些内核,程序可以与主流程上的UI进行交互,从而更新进度并处理用户交互。在同一处理器上执行多任务一无所获。这个程序不需要更多的球来处理,只需要更多的杂耍者。

另一方面,I / O操作不需要多个处理器。他们将请求分发到子系统或远程服务,然后等待响应。现在,这是一项多任务处理,可以节省时间——一次设置并监视多个任务,然后等待它们完成。等待时间取决于运行时间最长的任务,而不是任务总数。

串行运行所有内容,并且在运行任务时锁定用户界面。异步任务可以释放UI进程。在任务运行时,UI可以与用户交互。

任务、线程、计划、上下文

术语容易混淆。让我们尝试了解基本原理。

到底是怎么回事?

无论代码在何处运行,本地运行还是在云中某个地方运行,一个处理器和一个内核都意味着一个人在工作。操作系统可以像一个人一样执行多任务(请注意,该任务与DotNet Core任务无关),但是只有一个人一次可以做一件事。更多并行任务,更多任务之间切换时间,更少时间进行实际工作——听起来很熟悉!使用四个处理器或四个内核,您将有四个人在工作。没有主管,一个人正在运行代码,三个人告诉他该怎么做!

现在切换回DotNet Core。操作系统多任务通过线程(NOT任务)公开。任务是打包并在线程上运行的代码块。TaskScheduler组织和安排任务。它们基本上是状态机,可跟踪执行顺序和让步以组织有效的代码执行。

所有DotNet Core应用程序都在单个线程上启动,并且所有应用程序都有一个线程池——名义上每个处理器/内核一个(尽管在云中——仅云知道)。UI和Web服务器应用程序具有一个带有同步上下文的专用应用程序线程,以运行UI或HTTP/Web上下文。这是唯一可以直接访问UI / Web上下文的线程。控制台应用程序在threadpool线程上启动并运行。这种行为上的差异具有重要意义,我们将在后面讨论。在此讨论中,AppThread等于主应用程序线程。任务是通过SynchronisationContext.Post on the 同步上下文而不是直接加载到线程上的。

UI和Web上下文应用程序在同步上下文上运行所有内容,除非另有编码。决定在哪里运行任务并将其切换到线程池是程序员的责任。一旦进入线程池,任务就由线程池管理。

TaskSchedulers在线程上运行Task。每个线程一个。执行任务时,任务创建过程首先检查TaskScheduler.Current。如果线程上当前没有正在运行的任务,则该属性为null。如果TaskScheduler存在,则有一个任务正在运行,并且新任务已加载到当前TaskScheduler任务中。如果不存在,任务创建过程将在线程上检查SynchronisationContext。如果存在,它将通过SynchronisationContext计划新任务。如果没有SynchronisationContext,则任务创建过程中得到TaskScheduler.Default、threadpool调度并加载任务进入了threadpool。

现在让我们看一下经典模式。

private async Task UIMethodAsync(...)
{await code_Async;dependant_code_Sync;await code2_Async;dependant_code2_Sync;
}

扩展语法糖,您真正拥有的是:

public Task MethodAsync(...)
{code_Async().ContinueWith(task => {dependant_code_Sync;});code2_Async().ContinueWith(task => {dependant_code2_Sync;});
}

扩展版本显示了实际发生的情况,并在ContinueWith代码块内包装了await代码。第二个await仅在第一个Task完成后才被调用,因此两者是无关且独立的。

如果MethodAsync直接从UI / Web上下文调用(例如响应UI事件),则任务通过加载SynchronisationContext到应用程序线程上。所有代码都在该线程上运行。

现在让我们看一个稍微不同的实现:

private async Task MethodAsync(...)
{await Task.Run(() => code_Async);await code2_Async;
}

第一次等待将通过Task.Run(..)创建新任务。现在,我们正在管理任务的运行位置——特别是在线程池上运行的Task.Run()任务。第二个等待块在调用方法线程(可能是应用程序线程)上运行。

为确保所有内容都在threadpool上运行,请执行以下操作:

private async EventHandler(...) => await Task.Run(() => MethodAsync(...));private async Task MethodAsync(...)
{await code_Async;await code2_Async;
}

要以相同的方法并行运行多个任务,请执行以下操作:创建任务列表,然后使用WhenAll()来从任务运行它:

private async Task MethodAsync(...)
{var tasks = new List<Task>();tasks.Add(code_Async);tasks.Add(code2_Async);await Task.WhenAll(tasks);
}

Asnyc编码模式

Task对象是异步编程的核心。所有异步运行的方法都返回一个 Task:对此有一个例外,我们将在稍后讨论。可以将其Task视为代码块的包装对象。在内部,它就像一个状态机,跟踪执行顺序和收益。在外部,它提供有关代码块状态,一定级别的控制的信息,并在Task完成时公开返回值。

Async以及await旨在简化编码async方法的语法糖。

Async 将方法标记为异步并且:

  1. 使您可以使用await关键字等待异步任务的完成
  2. 完成时将任何返回值附加到任务对象

Await 挂起当前方法/代码块,并将控制权交还给它的调用者,直到任务完成。

命名约定

在作为任务运行的方法名称的末尾加上Async后缀是一种常见的做法。

异步任务模式

异步任务模式用于在您的方法中运行一个或多个任务的地方。您可以在同一方法中运行普通的同步代码。

经典模式:

private async Task MethodAsync(...)
{do_normal_stuff;_await do_some_work_Async;do_dependant_stuff;_
}

或者:

private async Task<myobject> MethodAsync(...)
{do_normal_stuff;_await do_some_work_Async;do_dependant_stuff;_return new myobject;
}

任务模式

这些模式不包含async关键字,但是可以异步运行。它们包含正常的代码,通常是相对较长的计算时间。对于async在接口和基类中声明方法也很有用。如果您标记不包含await的方法异步,则编译器会抱怨。

private Task MethodAsync(...)
{do some work;return Task.CompletedTask;
}

或者:

private Task<myobject> MethodAsync(...)
{do some work;return Task.FromResult(new myobject);
}

或者:

private Task MethodAsync(...)
{return  another_task_returning_method_of_the_same_pattern();
}

事件模式

public async void MethodAsync(...);

这是例外。它返回一个没有调用机制的控制机制的void。这是一个在后台运行直到完成的激活和遗忘模式。它只能用作事件处理程序。

阻塞与死锁

您将面临的最大挑战之一是死锁。始终锁定或在负载下锁定的异步代码。在Blazor中,这本身显示为锁定页面。灯亮了,但是家里没人。您已经杀死了运行SPA实例的应用程序进程。唯一的出路是重新加载页面(F5)。

正常原因是阻塞代码——在应用程序线程上的程序执行被暂停,以等待任务完成。该任务在线程的代码管道中进一步进行。暂停将阻止正在等待的代码的执行。僵局。如果将任务移至线程池,任务将完成并且块未阻止。但是,不会发生UI更新,也不会响应UI事件。将代码转移到任务池,以便您可以阻止应用程序线程不是答案。也不阻塞线程池线程。在负载下,您的应用程序可能会阻塞所有可用线程。

这是一些经典的阻止代码——在这种情况下,是UI中的按钮单击事件。

public void ButtonClicked()
{var task = this.SomeService.GetAListAsync();task.Wait();
}

和更多:

public void GetAListAsync()
{var task = myDataContext.somedataset.GetListAsync();var ret = task.Result;
}

Task.Wait()和task.Result正在阻止行动。他们停止在线程上执行,并等待任务完成。由于线程被阻塞而Task无法完成。

推荐建议

  1. 一路异步等待。不要混用同步和异步方法。从底部开始——数据或流程接口——并通过数据和业务/逻辑层一直到UI一直进行代码异步。Blazor组件同时实现异步和同步事件,因此,如果您的基本库提供了异步接口,则没有理由进行同步。
  2. 仅将处理器密集型任务分配给threadpool。不要因为你能就这么做。
  3. 不要在您的库中使用Task.Run()。将该决定尽可能保留在应用程序代码中。使您的库上下文无关。
  4. 切勿阻塞您的库。似乎很明显,但是...如果您绝对必须阻止,请在前端进行。
  5. 始终使用Async和Await,不要花哨。把事情简单化。
  6. 如果您的磁带库需要同时提供异步和同步调用,请分别对它们进行编码。“一次性编码”最佳实践不适用于此处。如果您不想在某个时候搬起石头砸自己的脚,切勿互相调用!
  7. 仅为事件处理程序使用Async Void。切勿在其他任何地方使用它。

现实世界中的一些例子

让我们看一些现实世界中的示例,这些示例稍微有些复杂,并且获得相同结果的替代方法。

该示例站点和代码存储库是与多个Blazor文章相关联的较大项目的一部分。

该站点位于Azure上。

可以在Github的CEC.Blazor Repository上找到该代码。

显示的代码在CEC.Blazor-Async运行。

该页面获取/计算薪水。它使用数据/控制器/ UI模式进行代码隔离。数据层作为Singleton服务运行,从Entitiy Framework源提取数据。业务/控制器层作为临时服务运行。

数据服务

该代码使用Task.Delay模拟查询延迟和同步FirstOrDefault以从本地列表对象获取记录,如下所示:

public async Task<EmployeeSalaryRecord> GetEmployeeSalaryRecord(int EmployeeID)
{await Task.Delay(2000);return this.EmployeeSalaryRecords.FirstOrDefault(item => item.EmployeeID == EmployeeID);
}

实时代码如下所示:

public async Task<EmployeeSalaryRecord> GetEmployeeSalaryRecord(int EmployeeID)
{return await mydatacontext.GetContext().EmployeeSalaryTable.FirstOrDefaultAsync(item => item.EmployeeID == EmployeeID);
}

请注意使用FirstorDefaultAsync进行EF调用,而不是FirstorDefault。检索列表也是如此——使用GetListAsync()。

控制器(业务层)服务

完整的代码块如下所示:

public async Task<decimal> GetEmployeeSalary(int employeeID, int egorating)
{this.Message = "Getting Employee record";this.MessageChanged?.Invoke(this, EventArgs.Empty);var rec = await this.SalaryDataService.GetEmployeeSalaryRecord(employeeID);if (rec.HasBonus){this.Message = "Wow big bonus to calculate - this could take a while!";this.MessageChanged?.Invoke(this, EventArgs.Empty);var bonus = await Task.Run(() => this.CalculateBossesBonus(egorating));this.Message = "Overpaid git!";return rec.Salary + bonus;}else{this.Message = "You need a rise!";return rec?.Salary ?? 0m;}
}

调用数据层是:

var rec = await this.SalaryDataService.GetEmployeeSalaryRecord(employeeID);

注意await。在将结果分配给局部变量时,我们正在等待(将控制权返回给调用方法),而不是阻塞。下面的代码块看起来几乎相同,但是它的块——NONO。

var rec = this.SalaryDataService.GetEmployeeSalaryRecord(employeeID).Result;

方法的第二部分检查是否需要计算奖金。奖励计算需要占用大量处理器,因此我们将其分流到另一个线程(否则,UI可能会变慢)。我们用Task.Run来切换线程/上下文。

var bonus = await Task.Run(() => this.CalculateBossesBonus(egorating));

阻止程序任务如下所示(请注意,它不会阻止,UI代码中的调用方会阻止):

public async Task<bool> BlockerTask()
{this.Message = "That's blown it.  F5 to get out of this one.";this.MessageChanged?.Invoke(this, EventArgs.Empty);await Task.Delay(1000);return true;
}

消息传递内容会更新显示消息,并告诉UI需要通过MessageChanged事件进行更新。

this.Message = "Getting Employee record";
this.MessageChanged?.Invoke(this, EventArgs.Empty);

UI代码

按钮单击与UI代码中的两个事件处理程序—— ButtonClicked和UnsafeButtonClicked连接起来。这两个事件处理程序都使用正确的异步事件处理程序模式—— async void。非阻塞代码如下所示:

public async void ButtonClicked(int employeeID)
{.....this.Salary = await this.SalaryControllerService.GetEmployeeSalary(employeeID, 3);...
}

虽然阻止代码如下所示:

public async void ButtonClicked(int employeeID)
{.....var x = this.SalaryControllerService.BlockerTask().Result;...
}

Index.razor代码中需要注意的其他几点:

  1. 有一些快速而肮脏的RenderTreeBuilder代码可以使活动感知按钮变得花哨。
  2. StateHasChanged()通过InvokeAsync调用。这可以确保它由Dispatcher在正确的应用程序线程上执行。
  3. 控制器服务的依赖注入。
  4. 使用Controller Service EventHandler控制组件UI的更新。

请注意,从库(EF)到UI事件的所有方法都是具有等待功能的async函数。一路异步。

我的知识的有用资源和来源

  • 异步编程-Microsoft
  • Stephen Cleary-任务导览和其他文章
  • Eke Peter-了解异步,避免C#中的死锁
  • Stephen Cleary-MSDN-异步编程最佳实践

了解和使用DotNetCore和Blazor中的异步编程相关推荐

  1. 一文说通C#中的异步编程补遗

    前文写了关于C#中的异步编程.后台有无数人在讨论,很多人把异步和多线程混了. 文章在这儿:一文说通C#中的异步编程 所以,本文从体系的角度,再写一下这个异步编程.   一.C#中的异步编程演变 1. ...

  2. 一文说通C#中的异步编程

    天天写,不一定就明白. 又及,前两天看了一个关于同步方法中调用异步方法的文章,里面有些概念不太正确,所以整理了这个文章.   一.同步和异步. 先说同步. 同步概念大家都很熟悉.在异步概念出来之前,我 ...

  3. 【转】.Net中的异步编程总结

    一直以来很想梳理下我在开发过程中使用异步编程的心得和体会,但是由于我是APM异步编程模式的死忠,当TAP模式和TPL模式出现的时候我并未真正的去接纳这两种模式,所以导致我一直没有花太多心思去整理这两部 ...

  4. .NET中的异步编程——常见的错误和最佳实践

    目录 背景 async void 没有线程 Foreach和属性 始终异步 在这篇文章中,我们将通过使用异步编程的一些最常见的错误来给你们一些参考. 背景 在之前的文章中,我们开始分析.NET世界中的 ...

  5. C#中的异步编程(Async)

    文章目录 C#中的异步编程(Async) 前言 示例代码 C#中的异步编程(Async) 前言 所谓的异步,就是指代码在运行的过程中,不会发生阻塞,例如我们玩游戏的时候,游戏在下载资源或者在加载本地资 ...

  6. .N“.NET研究”ET中的异步编程(二)- 传统的异步编程

    在上一篇文章中,我们从构建响应灵敏的界面以及构建高可伸缩性的服务应用来讨论我们为什么需要异步编程,异步编程能给我们带来哪些好处.那么知道了好处,我们就开始吧,但是在异步编程上海徐汇企业网站制作这个方面 ...

  7. NET中的异步编程(二)- 传统的异步编程

    转自:http://www.cnblogs.com/yuyijq/archive/2011/02/22/1960273.html 在上一篇文章中,我们从构建响应灵敏的界面以及构建高可伸缩性的服务应用来 ...

  8. 浅谈C#中的异步编程

    实现异步编程有4种方法可供选择,这4种访求实际上也对应着4种异步调用的模式,分为"等待"和"回调"两大类. Title一.使用EndInvoke: 二.使用Wa ...

  9. .NET中的异步编程(四)- IO完成端口以及FileStream.BeginRead

    本文首发在IT168 写这个系列原本的想法是讨论一下.NET中异步编程风格的变化,特别是F#中的异步工作流以及未来的.NET 5.0中的基于任务的异步编程模型.但经过三篇文章后很多人对IO异步背后实现 ...

最新文章

  1. Hive UDF初探
  2. 唠唠面试常问的:面向对象六大原则
  3. python做直方图-python实现直方图的应用
  4. MongoDB的简单操作
  5. SQL SERVER 使用 OPENRORWSET(BULK)函数将txt文件中的数据批量插入表中(2)
  6. 三重积分平均值_2015考研数学考前必须死磕的知识点
  7. MATLAB GUI如何制作下拉列表
  8. python 千万级数据处理_Python实现 ! 千万级别数据处理
  9. 注册表清理软件测试自学,注册表检测及修复工具(RegClean Pro)
  10. GD32VF103移植SVSTEMVIEW
  11. 微信小程序如何引入iconfont阿里巴巴素材库的图标
  12. 微信公众号网页开发步骤
  13. 建立完善的员工晋升机制_员工晋升管理制度精选5篇
  14. Hadoop经典书籍资料收藏(35本)转
  15. 国家官宣!硕士补贴30W、本科补贴20W!一线城市户口,最高5W生活补贴丨毕业去这些城市,太太太爽了!...
  16. 怎样设定目标(一)——目标金字塔
  17. ossec是干什么的?
  18. Gym - 101982E Cops And Robbers 网络流最小割
  19. 热爱生活阳光自信才能让自己快乐
  20. apicloud学习笔记

热门文章

  1. MySQL怎么查询课程信息_mysql 查询没有学全所有课程的同学的信息
  2. 期刊投稿状态_论文投稿,你不知道的那些事
  3. 算法与程序设计_算法与程序设计入门-简单计算题1
  4. win10 html css,Win10创造者更新:Edge支持CSS自定义属性
  5. _java等领域_测试、集成等领域最好的Java工具
  6. ipmitool介绍_ipmitool命令行使用详解
  7. 字体设计灵感合集|字体决定了设计
  8. webpack打包后的文件夹是空的_webpack打包Vue工程
  9. Linux内核深入理解定时器和时间管理(5):clockevents 框架
  10. GitHub#python#:用自组织映射解决旅行商问题