异步与并行的联系

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

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

图中操作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 

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

  1. C# 学习笔记——PL 并行编程 TPL 和传统 .NET 异步编程

    C# 学习笔记--PL 并行编程 TPL 和传统 .NET 异步编程 Task C# 多线程和异步模型 TPL模型 Task,异步,多线程简单总结 1,如何把一个异步封装为Task异步 Task.Fa ...

  2. python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)...

    python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程并行与并发同步与异步阻塞与非阻塞CPU密集型与IO密集型 线程与进程 进程 前言 ...

  3. java 多线程和并行编程_Java 8中的并行和异步编程

    java 多线程和并行编程 并行代码是在多个线程上运行的代码,曾经是许多经验丰富的开发人员的噩梦,但是Java 8带来了许多更改,这些更改应该使这种提高性能的窍门更加易于管理. 并行流 在Java 8 ...

  4. dnet 并行编程学习总结

    .Net并行编程高级教程--Parallel http://www.cnblogs.com/stoneniqiu/p/4857021.html 一直觉得自己对并发了解不够深入,特别是看了<代码整 ...

  5. 第一课-并行编程的几个概念

    为什么80%的码农都做不了架构师?>>>     为什么需要并行? 1. 业务要求 为了让用户有干好的产品使用感受,如ajax的异步请求,通过异步的执行让程序给用户带来的体验更加完美 ...

  6. python并行编程语言_Python3 系列之 并行编程

    Python Python开发 Python语言 Python3 系列之 并行编程 进程和线程 进程是程序运行的实例.一个进程里面可以包含多个线程,因此同一进程下的多个线程之间可以共享线程内的所有资源 ...

  7. cmake导入so库_libgo - 协程库、并行编程库

    libgo是一个使用C++11编写的协作式调度的stackful协程库, 同时也是一个强大的并行编程库, 是专为Linux服务端程序开发设计的底层框架. 目前支持三个平台: Linux (GCC4.8 ...

  8. 深入理解python异步编程_深入理解Python异步编程

    1 什么是异步编程 1.1 阻塞程序未得到所需计算资源时被挂起的状态. 程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上是阻塞的. 常见的阻塞形式有:网络I/O阻塞.磁盘I/O ...

  9. C#并行编程中的Parallel.Invoke

    一.基础知识 并行编程:并行编程是指软件开发的代码,它能在同一时间执行多个计算任务,提高执行效率和性能一种编程方式,属于多线程编程范畴.所以我们在设计过程中一般会将很多任务划分成若干个互相独立子任务, ...

最新文章

  1. mybatis-plus入坑指南
  2. Leetcode861翻转矩阵后的得分(C++题解):贪心
  3. Unity 之命名规范(一)
  4. android studio moudel,Android Studio将module变为library
  5. (238)数字IC工程师核心技能树(一)
  6. linux命令及其使用方法,Linux命令及其使用方法
  7. SpringCloud-服务的消费者(rest+ribbon)
  8. c# 反射技术杂谈。,
  9. 《团队-科学计算器-项目进度》
  10. 简单聊聊离散数学是什么
  11. Metric Learning详解(附带NCA算法)
  12. Python-教你用requests爬取网易云课堂热门课程。
  13. mac 怎么抓取 iphone 手机 日志
  14. 《管理学》第一章 管理与管理学
  15. STM32笔记(十二)---SPI读写FLASH
  16. vue3+ts+vite,使用高德api实现搜索城市展示在地图上
  17. word标题4降级标题5无法显示
  18. AES-256-CBC 加密解密
  19. ERP、PLM是什么意思?ERP、PLM有什么内在联系
  20. CRUX下实现进程隐藏(1)

热门文章

  1. java socket 报文解析_java socket解析和发送二进制报文工具(附java和C++转化问题)
  2. 了解cron以及使用cron定时备份MySQL
  3. 自定义Cell的流程
  4. maven,gradle本地缓存位置
  5. 在win10系统下怎样快速切换任务视图
  6. 利用 Node.js 实现 SAP Hana 数据库编程接口
  7. DOM 事件深入浅出(二)
  8. 解决nginx不支持pathinfo Thinkphp命名空间问题
  9. HTML meta refresh 刷新与跳转(重定向)页面
  10. 15个IT技术人员必须思考的问题