.Net异步编程 z
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);//模拟耗时操作,睡眠1s
Console.WriteLine("前台线程被调用!");
}
private static void Task2(object data)
{
Thread.Sleep(2000);//模拟耗时操作,睡眠2s
Console.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 == 0
orderby num descending
select 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()执行完毕";
});
}
是不是对执行结果感到惊讶?惊讶是对的,且听我们下面娓娓道来。
- 被async标记的方法,意味着可以在方法内部使用await,这样该方法将会在一个await point(等待点)处被挂起,并且在等待的实例完成后该方法被异步唤醒。【注意:await point(等待点)处被挂起,并不是说在代码中使用
await SomeMethodAsync()处就挂起,而是在进入SomeMethodAsync()真正执行异步耗时任务时被挂起,切记,切记!!!】 - async标记的方法,返回值类型为
void、Task、Task<T>。 - 被async标记的方法,方法的执行结果或者任何异常都将直接反映在返回类型中。
- 不是被async标记的方法,就会被异步执行,刚开始都是同步开始执行。换句话说,方法被async标记不会影响方法是同步还是异步的方式完成运行。事实上,async使得方法能被分解成几个部分,一部分同步运行,一些部分可以异步的运行(而这些部分正是使用await显示编码的部分),从而使得该方法可以异步的完成。
- 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. 总结
本文主要梳理了以下几点:
- 默认创建的Thread是前台线程,创建的Task为后台线程。
- ThreadPool创建的线程都是后台线程。
- 任务并行库(TPL)使用的是线程池技术。
- 调用async标记的方法,刚开始是同步执行的,只有当执行到await标记的方法中的异步耗时任务时,才会挂起。
.Net异步编程 z的更多相关文章
- 异步编程 z
走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $&qu ...
- C#异步编程 z
http://www.cnblogs.com/fangyz/p/5134018.html 从.NET4.5开始,用async和await关键字再加上Task.Run是一个非常不错的异步编程模型. 1. ...
- ASP.Net Core异步编程
ASP.Net Core异步编程 概念 什么是异步编程? 异步编程是可以让程序并行运行的一种手段,其可以让程序中的一个工作单元与主应用程序线程分开独立运行,并且在工作单元运行结束后,会通知主应用程序线 ...
- 使用Async和Await进行异步编程(C#版 适用于VS2015) z
你可以使用异步编程来避免你的应用程序的性能瓶颈并且加强总体的响应.然而,用传统的技术来写异步应用是复杂的,同时编写,调试和维护都很困难. VS2012介绍了简单的方法,那就是异步编程,它在.Net F ...
- C# 异步编程小结
APM 异步编程模型,Asynchronous Programming Model EAP 基于事件的异步编程模式,Event-based Asynchronous Pattern TAP 基于任务的 ...
- C#~异步编程再续~你必须要知道的ThreadPool里的throw
问题依旧存在 之前写过相关文章异步编程的文章,本文主要还是一点补充,之前在IIS经常发w3wp进程无做挂了的情况,但一直没能找到真正的原因,而查找相关资料,找了一些相关的文章,如await和async ...
- C#异步编程(一)
异步编程简介 前言 本人学习.Net两年有余,是第一次写博客,虽然写的很认真,当毕竟是第一次,肯定会有很多不足之处, 希望大家照顾照顾新人,有错误之处可以指出来,我会虚心接受的. 何谓异步 与同步相对 ...
- C#与C++的发展历程第三 - C#5.0异步编程巅峰
系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...
- 关于如何提高Web服务端并发效率的异步编程技术
最近我研究技术的一个重点是java的多线程开发,在我早期学习java的时候,很多书上把java的多线程开发标榜为简单易用,这个简单易用是以C语言作为参照的,不过我也没有使用过C语言开发过多线程,我只知 ...
随机推荐
- 20165203&20165206结对创意感想
一.结对学习过程 我和我的搭档性格志趣相投,而且各有所长,我们两个均属于一丝不苟的人,做一件事就要把它做好.因此,我们学习理念相同,志趣相投,这可能会占很大的优势.首先,我们会利用一周的前几天看课本, ...
- js对象的属性:数据(data)属性和访问器(accessor)属性
此文为转载,原文: 深入理解对象的数据属性与访问器属性 创建对象的方式有两种:第一种,通过new操作符后面跟Object构造函数,第二种,对象字面量方式.如下 var person = new Obj ...
- Delphi IdTCPClient IdTCPServer 点对点传送文件
https://blog.csdn.net/luojianfeng/article/details/53959175 2016年12月31日 23:40:15 阅读数:2295 Delphi ...
- IDEA创建Spring Boot项目
首先安装Spring Boot CLI 先确定自己安装的JDK是1.8版本或者以上,然后下载Srping Boot CLI,Spring Boot CLI下载地址,下载下来是一个压缩包,解压,得到一个 ...
- C语言:奇偶归一猜想
1.奇偶归一猜想——求多少步归一.(10分) 题目内容: 奇偶归一猜想——对于每一个正整数,如果它是奇数,则对它乘3再加1,如果它是偶数,则对它除以2,如此循环,最终都能够得到1. 如n = 11,得 ...
- 【51nod】1164 最高的奖励 V2
题解 一道比较神奇的二分图匹配 既然有n个元素,那么能匹配n个位置,我们把这n个位置找出来,是每个区间从左端点开始找到一个没有被匹配到的位置作为该点(我们忽略右端点) 然后我们从价值大到小,然后从左端 ...
- HTML5 Canvas游戏开发(二)高级功能
一.变形 1.放大和缩小 scale(X,Y)函数. 当使用该函数时,其起始坐标值也被放大或缩小.当X.Y为负值时,可以实现翻转. 2.平移变换 translate(X,Y)函数. 表示水平方向向左移 ...
- Data时间格式化
//时间戳转时间 function timeStamp2String(time) { var datetime = new Date(); datetime.setTime(time); var ye ...
- 前端安全系列之二:如何防止CSRF攻击?
背景 随着互联网的高速发展,信息安全问题已经成为企业最为关注的焦点之一,而前端又是引发企业安全问题的高危据点.在移动互联网时代,前端人员除了传统的 XSS.CSRF 等安全问题之外,又时常遭遇网络劫持 ...
- Eclipse插件安装出现Duplicate location错误
一.原因 1.曾今安装过此插件 2.曾今安装此插件的时候出现错误 二.解决方法[eclipse] - Help - Install new software - Available Software ...