本系列将和大家分享.Net中的异步多线程,本文是该系列的开篇。首先来看些概念:

进程:计算机概念,虚拟的概念,程序在服务器运行时占据全部计算资源的总和,我们给它起个名字叫进程。

线程:计算机概念,虚拟的概念,进程在响应操作时最小单位,也包含CPU 内存  网络  硬盘IO。

多线程:计算机概念,一个进程有多个线程同时运行。

进程与线程的区别:

  1、线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;

  2、一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线。

  3、进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;

  4、调度和切换:线程上下文切换比进程上下文切换要快得多。

  5、包含关系:没有线程的进程可以看作是单线程的。如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

线程和进程关系示意图:

总之,线程和进程都是一种抽象的概念,线程是一种比进程还小的抽象,线程和进程都可用于实现并发。在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位,它相当于一个进程里只有一个线程,进程本身就是线程。所以线程有时被称为轻量级进程。

后来,随着计算机的发展,对多个任务之间上下文切换的效率要求越来越高,就抽象出一个更小的概念——线程,一般一个进程会有多个(也可以是一个)线程。

任务调度:大部分操作系统的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态,等待下一个属于它的时间片的到来。这样每个任务都能得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”,这也就是我们所说的并发。

为何不使用多进程而是使用多线程?

线程廉价,线程启动比较快,退出比较快,对系统资源的冲击也比较小。而且线程彼此分享了大部分核心对象(File Handle)的拥有权。如果使用多重进程,它是不可预期的,且测试困难。

为什么可以多线程呢?

  1、多个CPU的核可以并行工作,4核8线程,这里的线程指的是模拟核。

  2、CPU分片,1s的处理能力分成1000份,操作系统调度着去响应不同的任务。从宏观角度来说,感觉就是多个任务在并发执行,从微观角度来说,一个物理cpu同一时刻只能为一个任务服务。

并行和并发:多核之间的叫并行,CPU分片的叫并发。

同步和异步:

  同步方法:发起调用,完成后才继续下一行;非常符合开发思维,有序执行。

  异步方法:发起调用,不等待完成,直接进入下一行,启动一个新线程来完成方法的计算。

至此相关概念介绍完了,下面我们正式进入本章主题:

C#里面的多线程:Thread类是C#语言对线程对象的一个封装。

为了演示此处我们使用VS2017建个Windows窗体应用程序MyAsyncThread,目标框架为:.NET Framework 4.6.1,如下所示:

此外,如果大家有使用过异步多线程的都知道,我们的异步多线程是不好调试的,一般我们会采用输出一些信息等方法来辅助我们调试和理解。对此我们有个小技巧,就是将应用程序的输出类型改为控制台应用程序,这样我们在启动程序的时候就会有2个窗口,一个是应用程序窗口,另外一个是控制台窗口。如下所示:

设置完,启动后就有2个窗口了,如下所示:

首先我们来看个很简单的同步方法:

/// /// 一个比较耗时耗资源的私有方法/// private void DoSomethingLong(string name){    Console.WriteLine($"****************DoSomethingLong Start  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");long lResult = 0;for (int i = 0; i < 1_000_000_000; i++)    {        lResult += i;    }//Thread.Sleep(2000);    Console.WriteLine($"****************DoSomethingLong   End  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");}

/// /// 同步方法/// private void btnSync_Click(object sender, EventArgs e){    Console.WriteLine($"****************btnSync_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");int l = 3;int m = 4;int n = l + m;for (int i = 0; i < 5; i++)    {string name = string.Format($"btnSync_Click_{i}");this.DoSomethingLong(name);    }    Console.WriteLine($"****************btnSync_Click   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");}

运行起来,点击同步方法后,会发现如果控制台还未输出完,此时WinForm窗口会卡在那里,无法拖动:

如果我们使用委托的BeginInvoke异步方法去执行会发现WinForm界面不会卡主:

/// /// 异步方法/// private void btnAsync_Click(object sender, EventArgs e){    Console.WriteLine($"****************btnAsync_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");    Action<string> action = this.DoSomethingLong;//action.Invoke("btnAsync_Click_1");//action("btnAsync_Click_1");//委托自身需要的参数+2个异步参数//action.BeginInvoke("btnAsync_Click_1", null, null);for (int i = 0; i < 5; i++)    {string name = string.Format($"btnAsync_Click_{i}");        action.BeginInvoke(name, null, null); //异步(开启一个子线程去完成)    }    Console.WriteLine($"****************btnAsync_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");}

此时控制台输出结果如下:

点击异步方法去执行,会发现马上可以拖动WinForm窗口,并不会像同步方法那样卡界面。那这是为什么呢?

通过对比同步方法和异步方法的执行结果会发现:

/// /// 异步方法/// 1 同步方法卡界面:主线程(UI线程)忙于计算,无暇他顾。///   异步多线程方法不卡界面:主线程闲置,计算任务交给子线程完成。///   改善用户体验,WinForm点击个按钮不至于卡死;///   Web应用发个短信通知,异步多线程去发短信;/// /// 2 同步方法慢,只有一个线程计算///   异步多线程方法快,因为5个线程并发计算///   12658ms   3636ms  不到4倍   CPU密集型计算(资源受限)///   10126ms   2075ms  差不多5倍,也不到5倍,Sleep(资源够用)///   多线程其实是资源换性能,1 资源不是无限的  2 资源调度损耗///   ///   一个订单表统计很耗时间,能不能多线程优化下性能?不能!这就是一个操作,没法并行///   需要查询数据库/调用接口/读硬盘文件/做数据计算,能不能多线程优化下性能?可以,多个任务可以并行///   线程不是越多越好,因为资源有限,而且调用有损耗///   /// 3 同步方法有序进行,异步多线程无序///   启动无序:线程资源是向操作系统申请的,由操作系统的调度策略决定,所以启动顺序随机///   同一个任务同一个线程,执行时间也不确定,CPU分片///   以上相加,结束也无序///   使用多线程请一定小心,很多事儿不是相当然的,尤其是多线程操作间有顺序要求的时候,///   通过延迟一点启动来控制顺序?或者预计下结束顺序?这些都不靠谱!///   ///   需要控制顺序,晚点分解!/// 

如果我们想在异步方法调用完之后再执行某些业务逻辑的话要怎么实现呢?(控制顺序)

方法1:调用BeginInvoke的时候传入回调函数。

/// /// 异步进阶/// private void btnAsyncAdvanced_Click(object sender, EventArgs e){    Console.WriteLine($"****************btnAsyncAdvanced_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");    Action<string> action = this.DoSomethingLong;//1 回调:将后续动作通过回调参数传递进去,子线程完成计算后,去调用这个回调委托    IAsyncResult asyncResult = null; //IAsyncResult是对异步调用操作的描述    AsyncCallback callback = ar =>    {        Console.WriteLine($"{object.ReferenceEquals(ar, asyncResult)}");        Console.WriteLine($"btnAsyncAdvanced_Click计算成功了。{ar.AsyncState}。{Thread.CurrentThread.ManagedThreadId.ToString("00")}");    };    asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "浪子天涯"); //第三个参数“浪子天涯”为回调传参    Console.WriteLine($"****************btnAsyncAdvanced_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");}

执行结果如下:

方法2:通过asyncResult.IsCompleted属性

为了演示此处将DoSomethingLong方法的执行时间调长一点

/// /// 一个比较耗时耗资源的私有方法/// private void DoSomethingLong(string name){    Console.WriteLine($"****************DoSomethingLong Start  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");long lResult = 0;for (int i = 0; i < 1_000_000_000; i++)    {        lResult += i;    }    Thread.Sleep(2000);    Console.WriteLine($"****************DoSomethingLong   End  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");}

/// /// 异步进阶/// private void btnAsyncAdvanced_Click(object sender, EventArgs e){    Console.WriteLine($"****************btnAsyncAdvanced_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");    Action<string> action = this.DoSomethingLong;//1 回调:将后续动作通过回调参数传递进去,子线程完成计算后,去调用这个回调委托    IAsyncResult asyncResult = null; //IAsyncResult是对异步调用操作的描述    AsyncCallback callback = ar =>    {        Console.WriteLine($"{object.ReferenceEquals(ar, asyncResult)}");        Console.WriteLine($"btnAsyncAdvanced_Click计算成功了。{ar.AsyncState}。{Thread.CurrentThread.ManagedThreadId.ToString("00")}");    };    asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "浪子天涯"); //第三个参数“浪子天涯”为回调传参//2 通过IsComplate等待,卡界面--主线程在等待,边等待边提示//( Thread.Sleep(200);位置变了,少了一句99.9999)int i = 0;while (!asyncResult.IsCompleted)    {Thread.Sleep(200); //放在这里不合适,因为有延迟,主线程在等待过程中的子线程可能已经完成了if (i < 9)        {            Console.WriteLine($"中华民族复兴完成{++i * 10}%....");        }else        {            Console.WriteLine($"中华民族复兴完成99.999999%....");        }//Thread.Sleep(200); //放在这里比较合适    }    Console.WriteLine("中华民族复兴已完成,沉睡的东方雄狮已觉醒!");    Console.WriteLine($"****************btnAsyncAdvanced_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");}

看下执行结果:

此处比我们预想的多了一个“...99.999999%”,那是因为有延迟,主线程在等待过程中的子线程可能已经完成了。故需要对Thread.Sleep(200)的位置做一个调整。

/// /// 异步进阶/// private void btnAsyncAdvanced_Click(object sender, EventArgs e){    Console.WriteLine($"****************btnAsyncAdvanced_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");    Action<string> action = this.DoSomethingLong;//1 回调:将后续动作通过回调参数传递进去,子线程完成计算后,去调用这个回调委托    IAsyncResult asyncResult = null; //IAsyncResult是对异步调用操作的描述    AsyncCallback callback = ar =>    {        Console.WriteLine($"{object.ReferenceEquals(ar, asyncResult)}");        Console.WriteLine($"btnAsyncAdvanced_Click计算成功了。{ar.AsyncState}。{Thread.CurrentThread.ManagedThreadId.ToString("00")}");    };    asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "浪子天涯"); //第三个参数“浪子天涯”为回调传参//2 通过IsComplate等待,卡界面--主线程在等待,边等待边提示//( Thread.Sleep(200);位置变了,少了一句99.9999)int i = 0;while (!asyncResult.IsCompleted)    {//Thread.Sleep(200); //放在这里不合适,因为有延迟,主线程在等待过程中的子线程可能已经完成了if (i < 9)        {            Console.WriteLine($"中华民族复兴完成{++i * 10}%....");        }else        {            Console.WriteLine($"中华民族复兴完成99.999999%....");        }Thread.Sleep(200); //放在这里比较合适    }    Console.WriteLine("中华民族复兴已完成,沉睡的东方雄狮已觉醒!");    Console.WriteLine($"****************btnAsyncAdvanced_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");}

此时的执行结果就是对的了:

方法3:WaitOne等待

{//3 WaitOne等待,即时等待 限时等待    asyncResult.AsyncWaitHandle.WaitOne();//直接等待任务完成    asyncResult.AsyncWaitHandle.WaitOne(-1);//一直等待任务完成    asyncResult.AsyncWaitHandle.WaitOne(1000);//最多等待1000ms,超时就不等了}

方法4:EndInvoke即时等待

{//4 EndInvoke  即时等待,而且可以获取委托的返回值 一个异步操作只能End一次    action.EndInvoke(asyncResult); //等待某次异步调用操作结束}

{    Func<int> func = () =>    {        Thread.Sleep(2000);return DateTime.Now.Hour;    };int iResult = func.Invoke();    IAsyncResult asyncResult = func.BeginInvoke(ar =>    {//int iEndResultIn = func.EndInvoke(ar); //在这里执行也是可以的,但是一个异步操作只能EndInvoke一次    }, null);int iEndResult = func.EndInvoke(asyncResult); //可以获取委托的返回值    Console.WriteLine($"iResult:{iResult}--iEndResult:{iEndResult}");}

Demo源码:

链接:https://pan.baidu.com/s/1qCXi1HsgBv_uFROIMfFYsw提取码:1nb4

本文概念部分参考博文:https://www.cnblogs.com/qianqiannian/p/7010909.html

此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/13543651.html  

版权申明:本文来源于网友收集或网友提供,如果有侵权,请转告版主或者留言,本公众号立即删除。

c# 多线程 执行事件 并发_.NET异步和多线程系列(一)相关推荐

  1. c# 多线程 执行事件 并发_C#.NET Thread多线程并发编程学习与常见面试题解析-1、Thread使用与控制基础...

    前言: 因为平时挺少用到多线程的,写游戏时都在用协程,至于协程那是另一个话题了,除了第一次学习多线程时和以前某个小项目有过就挺少有接触了,最近准备面试又怕被问的深入,所以就赶紧补补多线程基础. 网上已 ...

  2. 一则故事表达:并发,并行,同步,异步,线程,多线程

    一个小事件说明下并发,并行,同步,异步,线程,多线程 一个广交会举办向8间公司发起展览邀请, 参展公司有8间,场地有80万平方米的展示区域, 每个参展商有10万平方米可以用于展出售卖, 每个参展公司仅 ...

  3. python多线程实现生产者消费者_用Python实现多线程“生产者-消费者”模型的简单例子...

    用 Python 实现多线程"生产者 - 消费者"模型的简单例子 生产者消费者问题是一个著名的线程同步问题, 该问题描述如下: 有一个生产者在生产产品, 这些产品将提供给若干个消费 ...

  4. 多线程读取同一个文件_前端进阶:多线程Web Workers的工作原理及使用场景

    Web Worker 概述 Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行.在主线程运行的同时,Worker ...

  5. java 多线程并容器实现_跟着实例学习java多线程9-并发容器

    并发容器专门为并发而生的,最常用的就是ConcurrentHashMap.BlockingQueue了,这两个并发容器是我们比较常用的,前者取代同步Map提供了很好的并发性,后者提供了一种生产者与消费 ...

  6. python打开excel执行vba代码_“Python替代Excel Vba”系列(终):vba中调用Python

    请关注本号,后续会有更多相关教程. 系列文章 学Python还不会处理Excel数据?带你用pandas玩转各种数据处理"Python替代Excel Vba"系列(二):panda ...

  7. java多线程实例_多线程&高并发(全网最新:面试题+导图+笔记)面试手稳心不慌...

    前言 当你开始开始去跳槽面试的时候,明明只是一份15K的工作,却问你会不会多线程,懂不懂高并发,火箭造得让你猝及不防,结果就是凉凉:现如今市场,多线程.高并发编程.分布式.负载均衡.集群等可以说是现在 ...

  8. node和php处理高并发,node.js“多线程”如何处理高并发任务?,nodejs java 高并发

    node.js"多线程"如何处理高并发任务?node . js"多线程"是如何处理高度并发的任务的?,下面的文章介绍了使用nodejs"多线程&quo ...

  9. 异步与多线程的区别 线程安全 (总结)

    异步和多线程有什么区别?其实,异步是目的,而多线程是实现这个目的的方法.异步是说,A发起一个操作后(一般都是比较耗时的操作,如果不耗时的操作就没 有必要异步了),可以继续自顾自的处理它自己的事儿,不用 ...

最新文章

  1. matlab二重定积分_怎样用matlab求二重积分?
  2. tvnewpro 病毒清除
  3. nodejs源码_格物致知记一次nodejs源码分析的经历
  4. rooibos茶中单宁酸研究
  5. 集算器访问HTTP数据的代码示例
  6. java分布式会话redis_详解springboot中redis的使用和分布式session共享问题
  7. web前端时间戳转时间类型显示
  8. python 第一天
  9. ueditor分布式部署
  10. Atitit 法学处罚方式模式 目录 1. 申诫罚、财产罚和能力罚 1 1.1. 申诫罚 (警告和通报批评 ) 1 1.2. 财产罚是指使被处罚人的财产权利和利益受到损害的行政处罚。 2 1.2
  11. 哈尔滨工业大学计算机考研难吗,哈尔滨工业大学计算机考研经验:只有意志坚强才能到达彼岸...
  12. tamami来解答国内防辐射服为什么越卖越火
  13. PS:成功解决点击PS中的裁剪工具时,整张图片消失掉或者整张图片变的及其小的问题
  14. 阿里“小前台、大中台”的解读
  15. p29 p30 p31 p32
  16. log4j2日志输出到控制台-Maven工程
  17. pg_restore恢复备份(记录一下)
  18. 搜狗浏览器在高速模式下,右键点击才会出现“审查元素”
  19. 微信小程序实现电子签名并导出图片
  20. 树的遍历(Java)

热门文章

  1. 【elasticsearch】elasticsearch 熔断器
  2. 95-190-032-源码-window-CountWindow
  3. 【Flink】No tests found matching Method xx from org.junit.internal.requests.ClassRequest
  4. 【Flink】Flink启动报错 BindException: Could not start rest endpoint on any port in port range 7089
  5. Git报错:The file will have its original line endings in your working directory
  6. java代码使用Pair元组-运行可以-编译失败
  7. spark学习-Spark的mapPartitions与MapPartitionsWithIndex理解
  8. 不学spring其他,直接学spring boot
  9. 计算机系统结构开设学校,计算机系统结构专业介绍及考研院校排名
  10. ES6中的扩展运算符