C#并行编程(5):需要知道的异步

异步与并行的联系

大家知道“并行”是利用CPU的多个核心或者多个CPU同时执行不同的任务,我们不关心这些任务之间的依赖关系。
但是在我们实际的业务中,很多任务之间是相互影响的,比如统计车间全年产量的运算要依赖于各月产量的统计结果。假如你想在计算月产量的时候做些其他事情,如导出生产异常报表,“异步”就可以登上舞台了。

说到异步,必须要先提一下同步。一图胜千言:

640?wx_fmt=png

图中操作C的执行依赖B的结果,B的执行依赖A的结果。线程1连续执行操作A、B、C便是一个同步过程;相对地,线程1执行完A后把结果给线程2,线程2开始执行B,完成后把B的结果通知到线程1,线程1开始执行C,线程1在等待操作B结果的时候执行了D,这就是一个异步的过程;此外,异步过程中,B和D是并行执行的。

并行会提高业务的执行效率,但异步不会,异步甚至会拖慢业务的执行,比如上面A->B->C的执行过程。异步是让等待变得更有价值,这种价值则体现在多个业务的并行上

C#中的异步

在需要长时间等待的地方都可以使用异步,比如读写文件、访问网络或者处理图片。特别是在UI线程中,我们要保持界面的响应性,耗时的操作最好都使用异步的方式执行。

.NET提供了三种异步模式:

  • IAsyncResult模式(APM)

  • 基于事件的异步模式(EAP)

  • 基于任务的异步模式(TAP)

其中基于任务的异步模式是.NET推荐的异步编程方式。

IAsyncResult异步模式APM

下面是IAsyncResult基于委托的用法。





private delegate void AsyncWorkCaller(int workNo);

public static void Run()
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} will do some work.");
AsyncWorkCaller caller = DoWork;
AsyncCallback callback = ar =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} did the callback. [{ar.AsyncState}]");
};
IAsyncResult result = caller.BeginInvoke(1, callback, "callback msg");
DoWork(2);

caller.EndInvoke(result);
DoWork(3);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} done the work.");
}





private static void DoWork(int workNo)
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> work #{workNo} started with thread #{Thread.CurrentThread.ManagedThreadId}.");
Thread.Sleep(1000);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> work #{workNo} done with thread #{Thread.CurrentThread.ManagedThreadId}.");
}

我们使用BeginInvoke来异步执行作业1,同时可以执行作业2,调用EndInvoke的时候,当前线程被阻塞直到作业1完成。我们也可以使用result.AsyncWaitHandle.WaitOne()来等待异步作业完成,同样会阻塞当前线程。此外,可以为异步作业增加回调,异步作业在完成时会执行回调函数。

基于事件的异步模式EAP

事件大家不会陌生,我们在Winform编程的时候,总会用到事件。下面是利用BackgroundWorker实现的一个基于事件的简单异步过程。我们给异步对象(这里是BackgroundWorker)订阅DoWorkRunWorkCompleted事件,当调用RunWorkerAsync时,触发异步对象的工作事件,此时会开辟一个新线程来执行目标操作。目标操作完成时,触发工作完成事件,执行后续操作。与IAsyncResult模式不同的是,作业完成后的后续操作会在另外的一个线程执行,而IAsyncResult模式中,完成回调会在目标操作的执行线程中执行。

public static class EventBasedAsync
{
private static readonly BackgroundWorker worker = new BackgroundWorker();

static EventBasedAsync()
{
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
}

public static void Run()
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} will do some work.");
worker.RunWorkerAsync(1);
DoWork(2);
DoWork(3);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} done the work.");
}

private static void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} did something when work completed.");
}

private static void Worker_DoWork(object sender, DoWorkEventArgs e)
{
DoWork((int)e.Argument);
}


private static void DoWork(int workNo)
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> work #{workNo} started with thread #{Thread.CurrentThread.ManagedThreadId}.");
Thread.Sleep(3000);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> work #{workNo} done with thread #{Thread.CurrentThread.ManagedThreadId}.");
}
}

实际上,我们可以利用AsyncOperationManager实现自己的异步对象,可以使用dnSpy对BackgroundWorker进行反编译观察具体的实现过程。

基于任务的异步模式TAP

在《C#并行编程(4):基于任务的并行》中,我们已经总结过TaskTask<T>的用法,这里主要关注的是C#的async/await语法与Task的结合用法。

在C#中,我们使用async标记定义一个异步方法,使用await来等待一个异步操作。简单的用法如下:

public async Task DoWorkAsync()
{
await Task.Delay(1000);
}

public async Task<int> DoWorkAndGetResultAsync()
{
await Task.Delay(1000);
return 1;
}

async/await编写异步过程很方便,但异步方法的执行过程是怎样呢?下面的例子展示了一个异步操作的调用过程,我们以这个例子来分析异步方法的调用过程。

public static async Task Run()
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} will do some work.");

Task workTask1 = DoWork(1);

Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} got task #{workTask1.Id} by async call.");

Task workTask2 = DoWork(2);
await workTask2;
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} got task #{workTask2.Id} by async call.");

Task workTask3 = DoWork(3);
await workTask3;
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} got task #{workTask3.Id} by async call.");

Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> thread #{Thread.CurrentThread.ManagedThreadId} done the work.");
}






private static async Task DoWork(int workNo)
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> work #{workNo} started with thread #{Thread.CurrentThread.ManagedThreadId}.");
DateTime now = DateTime.Now;
await Task.Run(() =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> work #{workNo} was running by task #{Task.CurrentId} with thread #{Thread.CurrentThread.ManagedThreadId}.");
while (now.AddMilliseconds(3000) > DateTime.Now)
{
}
});
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}=> work #{workNo} done with thread #{Thread.CurrentThread.ManagedThreadId}.");
}

先来看一下例子的输出:

19:07:33.032779=> thread #10 will do some work.
19:07:33.039762=> work #1 started with thread #10.
19:07:33.075664=> thread #10 got task #2 by async call.
19:07:33.075664=> work #2 started with thread #10.
19:07:33.078658=> work #2 was running by task #3 with thread #11.
19:07:33.082647=> work #1 was running by task #1 with thread #6.
19:07:36.040739=> work #1 done with thread #6.
19:07:36.077638=> work #2 done with thread #11.
19:07:36.077638=> thread #11 got task #4 by async call.
19:07:36.077638=> work #3 started with thread #11.
19:07:36.077638=> thread #11 got task #7 by async call.
19:07:36.077638=> thread #11 done the work.
19:07:36.077638=> work #3 was running by task #6 with thread #12.
19:07:39.077652=> work #3 done with thread #12.

在上面的输出中,我们单看work #1,它由thread #10启动,计算过程在thread #6中执行并结束,最后任务在thread #10中返回,这里我们没有使用await来等待work #1的异步任务;假如我们使用await等待异步任务,如work #2,它在thread #10中启动,计算过程在thread #11中执行并结束,任务最后在thread #11中返回。大家可能发现了两者的不同:await改变了Run()方法的执行线程,从DoWork()方法的执行也能够看出,await会改变异步方法的执行线程!

实际上,编译器会把异步方法转换成状态机结构,执行到await时,编译器把当前正在执行方法(任务)挂起,当await的任务执行完成时,编译器再恢复挂起的方法,所以我们的输出中,异步方法await前面和后面的代码,一般是在不同的线程中执行的。编译器通过这种状态机的机制,使得等待异步操作的过程中线程不再阻塞,进而增强响应性和线程利用率。

理解异步方法的执行机制后,相信对异步的应用会变得更加娴熟,这里就不再总结异步的具体用法。

原文地址:https://www.cnblogs.com/chenbaoshun/p/10671403.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 
640?wx_fmt=jpeg

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

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

相关文章

从壹开始 [ Id4 ] 之一║ 授权服务器 IdentityServer4 开篇讲计划书

哈喽大家周四好&#xff01;时间过的很快&#xff0c;现在已经是三月份了&#xff0c;我的 IdentityServer4 教程也拖了一定的时间了&#xff0c;正好最近有精力学新东西了&#xff0c;主要中间被小伙伴要求写一个管理后台&#xff0c;目前1.0已经上线&#xff08;《权限后台系…

.NET 泛型,详细介绍

今天的文章是因为再给一个朋友讲这个的时候随手记录下整理出来的。说白了就是把前辈们曾经给我吹过的我又吹了出去。泛型&#xff1a;是C# FrameWork 2.0 时代 加入进来的&#xff0c;可以说对与Net开发人员来说泛型是无处不再的&#xff0c;喜欢看源码的同学&#xff0c;可能会…

P2257 YY的GCD

P2257 YY的GCD 题意&#xff1a; 求 1≤x≤N,1≤y≤M1 \leq x \leq N,1 \leq y \leq M1≤x≤N,1≤y≤M 且gcd(x, y) 为质数的 (x,y) 有多少对。 题解&#xff1a; 莫比乌斯反演 代码&#xff1a; #include <bits/stdc.h> #include <unordered_map> #define…

程序员修神之路--问世间异步为何物?

菜菜哥&#xff0c;今天天气挺热的&#xff0c;我都穿裙子了说吧&#xff0c;什么事&#xff1f;&#xff1f;苦笑一下..... 老大说把所有的接口都改成异步操作异步好呀&#xff0c;最少比同步能提高吞吐量异步是怎么回事呢&#xff0c;能讲讲不&#xff1f;来&#xff0c;凑近…

P3455 [POI2007]ZAP-Queries

P3455 [POI2007]ZAP-Queries 题意&#xff1a; 求满足1≤x≤a,1≤y≤b1\leq x\leq a,1\leq y\leq b1≤x≤a,1≤y≤b&#xff0c;且gcd(x,y)dgcd(x,y)dgcd(x,y)d的二元组(x,y)的数量 题解&#xff1a; 莫比乌斯反演板子 代码&#xff1a; // Problem: P3455 [POI2007]ZAP…

.NET Core 使用MailKit发送电子邮件

点击上方蓝字关注“汪宇杰博客”发送邮件通知的功能在各种系统里都很常见。我的博客也能在有新评论、新回复&#xff0c;或者文章被其他网站引用时向管理员发送邮件。那么在.NET Core里&#xff0c;如何实现发送电子邮件呢&#xff1f;准备工作我的案例会利用微软outlook.com的…

P3327 [SDOI2015]约数个数和

P3327 [SDOI2015]约数个数和 题意&#xff1a; 设 d(x) 为 x 的约数个数&#xff0c;给定 n,m&#xff0c;求 ∑i1n∑j1md(i,j)\sum_{i1}^{n}\sum_{j1}^{m}d(i,j)∑i1n​∑j1m​d(i,j) 题解&#xff1a; 代码&#xff1a; // Problem: P3327 [SDOI2015]约数个数和 // Conte…

C#并行编程(6):线程同步面面观

理解线程同步线程的数据访问在并行&#xff08;多线程&#xff09;环境中&#xff0c;不可避免地会存在多个线程同时访问某个数据的情况。多个线程对共享数据的访问有下面3种情形&#xff1a;多个线程同时读取数据&#xff1b;单个线程更新数据&#xff0c;此时其他线程读取数据…

P2522 [HAOI2011]Problem b

P2522 [HAOI2011]Problem b 题意&#xff1a; 对于给出的 n 个询问&#xff0c;每次求有多少个数对 (x,y)&#xff0c;满足 a≤x≤b&#xff0c;c≤y≤d&#xff0c;且 gcd(x,y)k&#xff0c;gcd(x,y) 函数为 x 和 y 的最大公约数。 题解&#xff1a; 这个题跟P3455 [POI20…

.NET Core/Framework 创建委托以大幅度提高反射调用的性能

都知道反射伤性能&#xff0c;但不得不反射的时候又怎么办呢&#xff1f;当真的被问题逼迫的时候还是能找到解决办法的。为反射得到的方法创建一个委托&#xff0c;此后调用此委托将能够提高近乎直接调用方法本身的性能。&#xff08;当然 Emit 也能够帮助我们显著提升性能&…

[省选联考 2020 A/B 卷] 冰火战士(树状数组上二分)

文章目录problemsolution(10pts)code(10pts)solution(30pts)code(30pts)solution(60pts)code(60pts)solution(100pts)code(100pts)problem luogu-P6619 一场比赛即将开始。 每位战士有两个属性&#xff1a;温度和能量。 有两派战士&#xff1a; 冰系战士的技能会对周围造成…

P1829 [国家集训队]Crash的数字表格 / JZPTAB

P1829 [国家集训队]Crash的数字表格 / JZPTAB 题意&#xff1a; 求∑i1n∑j1mlcm(i,j)\sum_{i1}^{n}\sum_{j1}^{m}lcm(i,j)∑i1n​∑j1m​lcm(i,j) 1<n<m<1e7 结果mod20101009 题解&#xff1a; 跟这个题P3911 最小公倍数之和很相近&#xff0c;但是本题数据范围大…

C# 跨设备前后端开发探索

每个人都拥有 好奇心&#xff0c;好奇心驱使着我们总是去尝试做一些有趣的事情。带起你的好奇心&#xff0c;本文将使用 C# 开发各种各样好玩的东西。每个人都拥有 好奇心&#xff0c;好奇心驱使着我们总是去尝试做一些有趣的事情。比如这件事&#xff1a;在好奇心的驱使下&…

Docker最全教程之Python爬网实战(二十二)

Python目前是流行度增长最快的主流编程语言&#xff0c;也是第二大最受开发者喜爱的语言&#xff08;参考Stack Overflow 2019开发者调查报告发布&#xff09;。笔者建议.NET、Java开发人员可以将Python发展为第二语言&#xff0c;一方面Python在某些领域确实非常犀利&#xff…

P2870 [USACO07DEC]Best Cow Line G

P2870 [USACO07DEC]Best Cow Line G 题意&#xff1a; 给你一个字符串&#xff0c;每次从首或尾取一个字符组成字符串&#xff0c;问所有能够组成的字符串中字典序最小的一个。 题解&#xff1a; 现在要组成字典序最小的&#xff0c;那我们每次就尽可能取小的 我们从两端开…

中间件是什么?在.NET Core中的工作原理又是怎样的呢?

本文出自《从零开始学ASP.NET CORE MVC》推荐文章&#xff1a;ASP.NET Core appsettings.json文件ASP.NET Core 中的中间件(Middleware)在这个视频中&#xff0c;我们将了解&#xff0c;ASP.NET Core 中的中间件是 什么&#xff1f;中间件很重要&#xff0c;尤其是在你想当架构…

从高德采集最新的省市区三级坐标和行政区域边界,用js在浏览器中运行

本文描述的是对国家统计局于2019-01-31发布的《2018年统计用区划代码和城乡划分代码(截止2018年10月31日)》中省市区三级的坐标和行政区域边界的采集。本文更新&#xff08;移步查阅&#xff09;&#xff1a;19-04-15 新采集了2018的省市区三级的坐标和行政区域边界数据csv格式…

Display Substring

Display Substring 题意&#xff1a; 一个长度为n的字符串&#xff0c;每个字符有自己的价值&#xff0c;求第k小价值的不重复子串价值 题解&#xff1a; 首先众所周知&#xff0c;所有子串都可以用后缀的前缀来表示&#xff0c;这就和后缀数组扯上关系了 我们可以直接二分…

使用 DotNet CLI 创建自定义的 WPF 项目模板

描述当我们安装完 DotNetCore 3.0 版本的 SDK 后&#xff0c;我们就可以创建基于 DotNetCore 的 WPF 项目模板&#xff0c;通过如下 CLI 可以方便快捷的创建并运行我们的项目&#xff1a;Copydotnet new wpf -n WpfAppcd WpfAppdotnet restoredotnet run做过 WPF 开发的朋友都知…

[翻译] ASP.NET Core 利用 Docker、ElasticSearch、Kibana 来记录日志

一步一步指导您使用 ElasticSearch, Kibana, ASP.NET Core 2.1 和 Docker 来记录日志在本教程中&#xff0c;我将向您展示如何启动和运行 ElasticSearch&#xff0c;Kibana 和 ASP.NET Core 2.1在开始之前&#xff0c;让我们来看看 ElasticSearch&#xff0c;Kibana 和 Serilog…