.Net异步编程知多少

1. 引言

最近在学习Abp框架,发现Abp框架的很多Api都提供了同步异步两种写法。异步编程说起来,大家可能都会说异步编程性能好。但好在哪里,引入了什么问题,以及如何使用,想必也未必能答的上来。
自己对异步编程也不是很了解,今天就以学习的目的,来梳理下同步异步编程的基础知识,然后再来介绍下如何使用async/await进行异步编程。下图是一张大纲,具体可查看脑图分享链接。

2. 同步异步编程

同步编程是对于单线程来说的,就像我们编写的控制台程序,以main方法为入口,顺序执行我们编写的代码。
异步编程是对于多线程来说的,通过创建不同线程来实现多个任务的并行执行。

3. 线程

.Net 1.0就发布了System.Threading,其中提供了许多类型(比如Thread、ThreadStart等)可以显示的创建线程。
说到Thread,我们需要了解以下几个概念:

3.1. 什么是主线程

每一个Windows进程都恰好包含一个用作程序入口点的主线程。进程的入口点创建的第一个线程被称为主线程。.Net执行程序(控制台、Windows Form、Wpf等)使用Main()方法作为程序入口点。当调用该方法时,主线程被创建。

3.2. 什么是工作者线程

由主线程创建的线程,可以称为工作者线程,用来去执行某项具体的任务。

3.3. 什么是前台线程

默认情况下,使用Thread.Start()方法创建的线程都是前台线程。前台线程能阻止应用程序的终结,只有所有的前台线程执行完毕,CLR才能关闭应用程序(即卸载承载的应用程序域)。前台线程也属于工作者线程。

3.4. 什么是后台线程

后台线程不会影响应用程序的终结,当所有前台线程执行完毕后,后台线程无论是否执行完毕,都会被终结。一般后台线程用来做些无关紧要的任务(比如邮箱每隔一段时间就去检查下邮件,天气应用每隔一段时间去更新天气)。后台线程也属于工作者线程。

说了这么多概念不如来段代码:

 //主线程入口static void Main(string[] args) {Console.WriteLine("主线程开始!");     //创建前台工作线程Thread t1 = new Thread(Task1);t1.Start();     //创建后台工作线程Thread t2= new Thread(new ParameterizedThreadStart(Task2));t2.IsBackground = true;//设置为后台线程t2.Start("传参");} private static void Task1() {Thread.Sleep(1000);//模拟耗时操作,睡眠1sConsole.WriteLine("前台线程被调用!");} private static void Task2(object data) {Thread.Sleep(2000);//模拟耗时操作,睡眠2sConsole.WriteLine("后台线程被调用!" + data);}


执行发现,【后台线程被调用】将不会显示。因为当所有的前台线程执行完毕后,应用程序就关闭了,不会等待所有的后台线程执行完毕,所以不会显示。

4. ThreadPool(线程池)

线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率,这也是线程池的主要好处。
ThreadPool适用于并发运行若干个任务且运行时间不长且互不干扰的场景。
还有一点需要注意,通过线程池创建的任务是后台任务。

举个例子:

//主线程入口static void Main(string[] args){Console.WriteLine("主线程开始!");    //创建要执行的任务WaitCallback workItem = state => Console.WriteLine("当前线程Id为:" + 
Thread.CurrentThread.ManagedThreadId);    //重复调用10次for (int i = 0; i < 10; i++){ThreadPool.QueueUserWorkItem(workItem);}Console.ReadLine(); }


从图中可以看出,程序并没有每次执行任务都创建新的线程,而是循环利用线程池中维护的线程。
如果去掉最后一句Consoler.ReadLine(),会发现程序仅输出【主线程开始!】就直接退出,从而确定ThreadPool创建的线程都是后台线程。

5. System.Threading.Tasks

.Net 4.0引入了System.Threading.Tasks,简化了我们进行异步编程的方式,而不用直接与线程和线程池打交道。
System.Threading.Tasks中的类型被称为任务并行库(TPL)。TPL使用CLR线程池(说明使用TPL创建的线程都是后台线程)自动将应用程序的工作动态分配到可用的CPU中。

5.1. Parallel(数据并行)

数据并行是指使用Parallel.For()或Parallel.ForEach()方法以并行方式对数组或集合中的数据进行迭代。
看怎么用:

ParallelLoopResult result = Parallel.For(0, 10000, i => {Console.WriteLine("{0}, task: {1} , thread: {2}", i, Task.CurrentId, 
Thread.CurrentThread.ManagedThreadId); });

5.2. PLINQ(并行LINQ查询)

为并行运行而设计的LINQ查询为PLINQ。System.Linq命名空间的ParallelEnumerable中包含了一些扩展方法来支持PINQ查询。
使用举例:

int[] modThreeIsZero = (from num in source.AsParallel()       
                 where num % 3 == 0orderby num descendingselect num).ToArray();

5.3. Task

Task,字面义,任务。使用Task类可以轻松地在次线程中调用方法。

static void Main(string[] args){Console.WriteLine("主线程ID:" + Thread.CurrentThread.ManagedThreadId);Task.Factory.StartNew(() => Console.WriteLine("Task对应线程ID:" 
+ Thread.CurrentThread.ManagedThreadId));Console.ReadLine(); }


可以看见,使用Task我们不必理会具体线程的创建。
我们也可以使用.NET 4.5引入的Task.Run静态方法来启动一个线程。

static void Main(string[] args){Console.WriteLine("主线程ID:" + Thread.CurrentThread.ManagedThreadId);Task.Run(() => Console.WriteLine("Task对应线程ID:" + 
Thread.CurrentThread.ManagedThreadId));Console.ReadLine(); }

Task类提供了Wait()方法,用来等待线程task执行完毕。

5.4. Task

Task 是Task的泛型版本,可以接收一个返回值。

static void Main(string[] args){Console.WriteLine("主线程ID:" + Thread.CurrentThread.ManagedThreadId);Task<string> task = Task.Run(() =>{        return Thread.CurrentThread.ManagedThreadId.ToString();});Console.WriteLine("创建Task对应的线程ID:" + task.Result);Console.ReadLine();
}


Task提供了很多方法,帮助我们进行异步任务。了解更多,可参考MSDN。

5.5. async/await 特性

C# async关键字用来指定某个方法、Lambda表达式或匿名方法自动以异步的方式来调用。

咱们先来看一个具体的示例吧。

static void Main(string[] args){Console.WriteLine("主线程启动,当前线程为:" + Thread.CurrentThread.ManagedThreadId);Task<int> task = GetLengthAsync();Console.WriteLine("回到主线程,当前线程为:" + Thread.CurrentThread.ManagedThreadId);Console.WriteLine("task的返回值是" + task.Result);Console.WriteLine("主线程结束,当前线程为:" + Thread.CurrentThread.ManagedThreadId);
}static async Task<int> GetLengthAsync(){Console.WriteLine("GetLengthAsync()开始执行,当前线程为:"

+ Thread.CurrentThread.ManagedThreadId);    var str = await GetStringAsync();Console.WriteLine("GetLengthAsync()执行完毕,当前线程为:"
+ Thread.CurrentThread.ManagedThreadId);    return str.Length; }static Task<string> GetStringAsync(){Console.WriteLine("-----------------");Thread.Sleep(5000);Console.WriteLine("GetStringAsync()开始执行,当前线程为:"
+ Thread.CurrentThread.ManagedThreadId);    return Task.Run(() =>{Console.WriteLine("异步任务开始执行,当前线程为:"
+ Thread.CurrentThread.ManagedThreadId);Thread.Sleep(5000);Console.WriteLine("GetStringAsync()执行完毕,当前线程为:"

+ Thread.CurrentThread.ManagedThreadId);      
 return "GetStringAsync()执行完毕";}); }


是不是对执行结果感到惊讶?惊讶是对的,且听我们下面娓娓道来。

  1. 被async标记的方法,意味着可以在方法内部使用await,这样该方法将会在一个await point(等待点)处被挂起,并且在等待的实例完成后该方法被异步唤醒。【注意:await point(等待点)处被挂起,并不是说在代码中使用await SomeMethodAsync()处就挂起,而是在进入SomeMethodAsync()真正执行异步耗时任务时被挂起,切记,切记!!!】

  2. async标记的方法,返回值类型为voidTaskTask<T>

  3. 被async标记的方法,方法的执行结果或者任何异常都将直接反映在返回类型中。

  4. 不是被async标记的方法,就会被异步执行,刚开始都是同步开始执行。换句话说,方法被async标记不会影响方法是同步还是异步的方式完成运行。事实上,async使得方法能被分解成几个部分,一部分同步运行,一些部分可以异步的运行(而这些部分正是使用await显示编码的部分),从而使得该方法可以异步的完成。

  5. await关键字告诉编译器在async标记的方法中插入一个可能的挂起/唤醒点。 逻辑上,这意味着当你写await someMethod();时,编译器将生成代码来检查someMethod()代表的操作是否已经完成。如果已经完成,则从await标记的唤醒点处继续开始同步执行;如果没有完成,将为等待的someMethod()生成一个continue委托,当someMethod()代表的操作完成的时候调用continue委托。这个continue委托将控制权重新返回到async方法对应的await唤醒点处。
    返回到await唤醒点处后,不管等待的someMethod()是否已经经完成,任何结果都可从Task中提取,或者如果someMethod()操作失败,发生的任何异常随Task一起返回或返回给SynchronizationContext

从第4点可以解释为什么上面的demo当调用GetLengthAsync();方法时,输出GetLengthAsync()开始执行,当前线程为:1
从第1点可以解释调用await GetStringAsync();后,为什么程序会继续同步执行输出GetStringAsync()开始执行,当前线程为:1,且会继续执行异步任务输出异步任务开始执行,当前线程为:3,而直到代码执行到Thread.Sleep(5000)才会返回到主线程输出回到主线程,当前线程为:1。这里Thread.Sleep(5000)就是await point(等待点)。
回到主线程后,因为要输出task.Result,所以主线程会等待。当异步任务完成后会输出GetStringAsync()执行完毕,当前线程为:3
从第5点可以解释,await等待异步任务完成后,GetLengthAsync()方法被异步唤醒,从而异步执行后续代码而输出GetLengthAsync()执行完毕,当前线程为:3

6. 总结

本文主要梳理了以下几点:

  1. 默认创建的Thread是前台线程,创建的Task为后台线程。

  2. ThreadPool创建的线程都是后台线程。

  3. 任务并行库(TPL)使用的是线程池技术。

  4. 调用async标记的方法,刚开始是同步执行的,只有当执行到await标记的方法中的异步耗时任务时,才会挂起。


相关文章:

  • 深入理解Async/Await

原文地址:http://www.cnblogs.com/sheng-jie/p/6471986.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

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

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

相关文章

指纹识别开发1.0

在不久之前&#xff0c;用java和C#分别开发了个人脸识别&#xff0c;感觉挺不错的&#xff0c;于是脑袋一发热&#xff0c;想了想能不能搞个指纹识别&#xff0c;答案当然是能&#xff0c;那么问题来了&#xff0c;在人脸识别的时候可以借助自带摄像头提取你的face&#xff0c;…

DFS VS BFS

实际案例 代码实现 package com.atguigu.graph;import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList;/*** 创建人 wdl* 创建时间 2021/4/2* 描述*/ public class Graph {private ArrayList<String> vertexList;//存储顶点集合private in…

CLR运行时细节 - Method Descriptor

方法描述符:MethodDesc 运行时用来描述类型的托管方法,它保存在方法描述桶(MethodDescChunk)内;方法描述符保存了方法在运行时的一些重要信息:是否JIT编译;是否有方法表槽(决定了方法入口是跟在方法描述符(MethodDesc)后还是在方法表(MethodTable)后面);距离MethodDescChunk的索…

2015蓝桥杯省赛---java---B---3(三羊献瑞)

题目 三羊献瑞 思路分析 由于是填空题&#xff0c;没有时间和内存的要求&#xff0c;所以看到这个题&#xff0c;第一想法就是暴力破解&#xff0c;当然了&#xff0c;怎么快就怎么做。 由于 "三"是数字的首位&#xff0c;低位的数字进位后必然为1&#xff0c;所…

mysql common是什么_MySQL common_schema简介

common_schema为MySQL提供了查询脚本&#xff0c;分析并且信息化的视图和一个函数库&#xff0c;以便更容易的管理和诊断。它引入的一些基于SQL的工具简common_schema的简介&#xff1a;Shlomi Noach 的common_schema项目()是一套针对服务器脚本化和管理的强大的代码和视图。co…

数据库权限分配探讨

上周&#xff0c; 有位朋友给我提出了这样的需求&#xff1a;区分用户访问数据库的权限。顺便总结了下有如下要求&#xff1a; 某个用户查询所有数据库的权限 某个用户只有备份数据库的权限 给一个用户只能查看指定数据库的权限 给一个用户只有某个表的权限 要进行以上任务&…

2015蓝桥杯省赛---java---B---6(加法变乘法)

题目 加法变乘法 思路分析 两个算式进行相减操作 代码实现 package com.atguigu.lanqiao;public class Main { // 简单枚举public static void main(String[] args) {for (int i 1; i < 46; i) {for (int j i 2; j < 48; j) {if (i * (i 1) - (i i 1) j * (…

DI 之Spring更多DI的知识

转载自 【第三章】 DI 之 3.3 更多DI的知识 ——跟我学spring3 3.3.1 延迟初始化Bean 延迟初始化也叫做惰性初始化&#xff0c;指不提前初始化Bean&#xff0c;而是只有在真正使用时才创建及初始化Bean。 配置方式很简单只需在<bean>标签上指定 “lazy-init” 属性值…

Microsoft发布新工具,简化JavaScript Web应用的创建

Microsoft发布了一系列工具用于快速生成基于JavaScript的Web应用&#xff0c;这些工具包含了dotnet new和对Node.js的巧妙运用。 dotnet new命令是.NET Core工具的一部分&#xff0c;它用简单的命令行语法创建&#xff08;spin up&#xff09;新项目。现在新工具已成为ASP.NET …

2015蓝桥杯省赛---java---B---7(牌型种数)

题目 牌型种数 思路分析 递归进行实现&#xff0c;弄好终止条件&#xff0c;牌的数目等于13 代码实现 package com.atguigu.lanqiao;public class Main { // 简单枚举private static int ans;public static void main(String[] args) {f(0, 0);System.out.println(ans);}…

CLR运行时细节 - 继承多态的实现

关于多态不多解释了,在运行时决定和调用具体的实现,是面向对象的基础 设计模式的基础.准备把继承多态和接口多态分开,因为从CLR实现的角度继承多态相比于接口多态要简单得多,也更容易理解,本篇只讨论继承多态, .NET Framework 2.0 和 4.0 这两个版本在实现上稍微有点区别(这里先…

2015蓝桥杯省赛---java---B---8(饮料换购)

题目 饮料换购 饮料换购乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料&#xff0c;凭3个瓶盖可以再换一瓶C型饮料&#xff0c;并且可以一直循环下去&#xff0c;但不允许赊账。请你计算一下&#xff0c;如果小明不浪费瓶盖&#xff0c;尽量地参加活动&#xff0c;那么…

python123测验9程序题_python程序设计实验二

Python程序设计实验安徽工程大学班级&#xff1a;物流191 姓名&#xff1a;许岚岚 学号&#xff1a;3190505110日期&#xff1a;2020年3月21日指导教师&#xff1a;修 宇实验二 顺序结构程序设计(验证性实验)(二学时)【实验目的】(1)掌握数据的输入输出的方法&#…

Rider IDE恢复了对.NET Core调试的支持

近期发布的JetBrain的Rider IDE&#xff08;EAP17&#xff09;移除了对.NET Core调试支持。该功能与NuGet的一个软件包在许可上存在冲突&#xff0c;而EAP17使用NuGet提供的.NET Core项目调试功能&#xff0c;所以必须要移除该功能。为此&#xff0c;JetBrains迅速推出了Rider …

2016蓝桥杯省赛---java---B---1(煤球数目)

题目 煤球数目 思路分析 代码实现 package com.atguigu.lanqiao;import java.util.Scanner;public class Main { // 简单枚举public static void main(String[] args) { // 171700int pre 1;int plus 2;long sum 1;for (int k 2; k < 100; k) {sum (pre plus); //…

vue 多页面多模块分模块打包 分插件安装_Vue渲染方式

Vue中的渲染方式总结可分四种:原有模板语法&#xff0c;挂载渲染使用render属性&#xff0c;createElement函数直接渲染使用render属性&#xff0c;配合组件的template属性&#xff0c;createElement函数渲染使用render属性&#xff0c;配合单文件组件&#xff0c;createElemen…

零配置 之 Spring注解实现Bean定义

转载自 零配置 之 12.3 注解实现Bean定义 ——跟我学spring3 12.3 注解实现Bean定义 12.3.1 概述 前边介绍的Bean定义全是基于XML方式定义配置元数据&#xff0c;且在【12.2注解实现Bean依赖注入】一节中介绍了通过注解来减少配置数量&#xff0c;但并没有完全消除在XML…

开源库 Natasha2016 ,让IL编程跑起来

背景&#xff1a; IL编程在普通的程序员的代码里几乎不会出现&#xff0c;但从Json.net、Dapper、Asp.net等等开源项目都体现出了IL编程的重要性。 在IL编程的时候&#xff0c;上百行甚至上千行的IL代码实在让人头大&#xff0c;调试不方便不说&#xff0c;IL编程的逻辑也是不同…

代码随想录27期|Python|Day24|回溯法|理论基础|77.组合

图片来自代码随想录 回溯法题目目录 理论基础 定义 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 回溯是递归的副产品&#xff0c;只要有递归就会有回溯。回溯函数也就是递归函数&#xff0c;指的都是一个函数。 基本问题 组合问题&#xff08;无序&…

指纹识别软件安装包下载

微信关注公众号&#xff1a;1111的博客&#xff0c;回复“指纹识别”&#xff0c;即可获取指纹识别安装包的下载链接。本软件的版本为指纹识别2.0应用程序&#xff0c;在安装的过程中有以下几个注意点【一定要看】&#xff1a;安装完成之后直接运行&#xff1a;指纹识别.exe文件…