C#异步编程基础入门总结
1.前言
*.NET Framework提供了执行异步操作的三种模式:
异步编程模型(APM)模式(也称为IAsyncResult的模式),其中异步操作要求Begin和End方法(例如,BeginWrite和EndWrite异步写入操作)。这种模式不再被推荐用于新开发。有关更多信息,请参阅异步编程模型(APM)。
基于事件的异步模式(EAP),它需要一个具有Async后缀的方法,并且还需要一个或多个事件,事件处理程序委托类型和被EventArg派生类型。EAP在.NET Framework 2.0中引入。不再推荐新的开发。有关更多信息,请参阅基于事件的异步模式(EAP)。
基于任务的异步模式(TAP),它使用单一方法来表示异步操作的启动和完成。TAP在.NET Framework 4中引入,是.NET Framework中推荐的异步编程方法。C#中的async和等待关键字,Visual Basic语言中的Async和Await运算符为TAP添加语言支持。有关更多信息,请参阅基于任务的异步模式(TAP)。*
2.异步的应用场景
在计算机程序的运行中,计算是需要一定的时间的,在运算时间过长的任务时,比如上传大文件、读取文件流、数据库操作、httprequest等,如果是同步(synvronous)必须等待该任务执行完成才能继续下一个任务。使用异步(asynchronous)操作,会开启新的线程,不会等待异步操作完成才去执行后面的程序,相比异步编程优点:1.就是出现长时间处理程序时,不会卡界面,用户仍然可以操作UI界面2.提高程序运行效率,节约CPU资源,提供系统吞吐量。
3.进程和线程的关系
这个面试的时候基本上都会问到,简而言之就是:
一个程序都会有一个进程和一个线程,进程是由CPU进行调度分配资源的,有一个完整的虚拟地址空间,不依赖线程独立存在,反之线程是由进程来调度分配的,只是进程的一部分,没有自己的地址空间,与进程内的其他线程一起共享该进程的所有资源。打个简单的比方就像是线程就好比是人体的寄生虫,不能独立存在,必须依靠人(进程)的营养(资源)来生存(执行)
4.异步和多线程的区别
异步是相对同步而言的,我们知道异步是开启了新线程,但是和多线程不是一个概念,异步相当于一个人的“大脑”能够做试卷,又能够看电影,同时处理两件以上不同的事情。多线程好比多个人做不同的事情。
异步操作的本质
c#中异步和多线程的区别是什么呢?异步和多线程两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。 所有的程序最终都会由计算机硬件来执行,无须消耗CPU时间的I/O操作正是异步操作的硬件基础。
线程的本质
线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度
异步操作的优缺点
因为异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少 共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些出入,而且难以调试。
多线程的优缺点
多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现。
适用范围
当需要执行I/O操作时,使用异步操作比使用线程+同步 I/O操作更合适。I/O操作不仅包括了直接的文件、网络的读写,还包括数据库操作、Web Service、HttpRequest以及.net Remoting等跨进程的调用。
而线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行。但是往往由于使用线程编程的简单和符合习惯,所以很多朋友往往会使用线程来执行耗时较长的I/O操作。这样在只有少数几个并发操作的时候还无伤大雅,如果需要处理大量的并发操作时就不合适了。
5.C#异步方式之一( BeginInvoke、EndInvoke方法)
方式1:使用回调方法完成异步委托
先来看个例子,委托的异步调用,这个例子首先定义一个string类型的返回值、string类型的参数的委托。虽然这中模式不推荐被使用。
class Program
{
delegate string SayHi(string name);//定义委托
static void Main(string[] args)
{
SayHi sayhi = new SayHi(SayHiName);//实例化委托
sayhi("科比");//一般的直接调用
sayhi.Invoke("张林");//使用Invoke方法同步调用
//异步调用
sayhi.BeginInvoke("杜兰特", (IAsyncResult ar) =>
{
sayhi.EndInvoke(ar);
Console.WriteLine("打招呼成功结束");
}, null);
}
public static string SayHiName(string name)
{
return "how are you"+name + "?";
}
}
前两种调用委托的方式都是同步的,BeginInvoke方法的返回值是IAsyncResult类型的
该方法的参数由两部分组成,前面(n)个参数是委托的参数,倒数第二个参数也表示一个委托,该委托是.net系统定义的委托(和func、action类似),查看AsyncCallback的定义如图:
作用就是:作为执行调用的回调方法,值得注意的是,在回调方法中,必须调用EndInvoke方法结束异步调用,EndInvoke是获取异步调用的结果
上面的例子调试的结果如图:
方式2:使用轮询
我们把BeginInvoke的委托参数为null,使用轮询的方式
Func<string, string> func = delegate (string name)
{
Thread.Sleep(2000); return "how are you" + name + "";
};
IAsyncResult ar = func.BeginInvoke("张林",null,null);
int i = 1;
while (!ar.IsCompleted)
{
Console.WriteLine(200*i);
i++;
Thread.Sleep(200);
}
string result = func.EndInvoke(ar);
Console.WriteLine(result);
结果如图:
6.C#异步方式之二 await async
async和await是一对关键字,它是.net 4.5的特性。在实际工作中使用方便灵活,主要原因就是可以像写同步方法那样去异步编程,代码结构清晰,不用关心如何实现异步的编程。
这里其实要注意的是,之前刚说了异步是开启新的线程来实现的,但是await 和async两个关键字并没有开启新的线程,为了证明这一点,下面建了一个winform的程序,异步获取图片并显示到picturebox上。
public Form1()
{
InitializeComponent();
this.label1.Text = "主线程Id:"+Thread.CurrentThread.ManagedThreadId;
} private async void button1_Click(object sender, EventArgs e)
{
string imageUrl = "https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=3850265187,1181041963&fm=173&s=62E19A4722716A371EB097FB03009015&w=218&h=146&img.JPEG";
HttpClient client = new HttpClient();
var response =await client.GetAsync(imageUrl);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var stream =await response.Content.ReadAsStreamAsync();
Image image = Bitmap.FromStream(stream,true);
this.label2.Text = "线程Id:" + Thread.CurrentThread.ManagedThreadId;
this.pictureBox1.BackgroundImage = image;
}
}
其实不用看图就已经知道答案了,程序运行时不报异常,就已经说明一点:await async两个关键根本创建新的线程。这个涉及到异步更新UI到主线程,就不多说了。
结果如图:
async await方法的使用说明:
- 返回类型: void 、Task、Task<泛型类型>
- async、await不会创建新的线程,实现等待的效果,必须同时使用
- 使用该方法的方法主体也要用async关键字
异步方法事例:
private static async Task<int> GetValueAsync(int a)
{
//Task.run 开启了新的线程
await Task.Run(() =>
{
Thread.Sleep(2000); //模拟耗时
Console.WriteLine("GetValueAsync方法结束,线程ID:" + Thread.CurrentThread.ManagedThreadId);
return a * a;
});
Console.WriteLine("线程ID:" + Thread.CurrentThread.ManagedThreadId+"异步方法结束");
return a * a;
}
调用异步方法:
private async void button1_Click(object sender, EventArgs e)
{
int result =await GetValueAsync(5);
this.label1.Text = "异步计算的结果" + result + "线程ID:" + Thread.CurrentThread.ManagedThreadId;
}
7.C#异步方式之三 浅谈Task
前面刚刚了解到async await是.net 4.5出的特性,Task是.net4.0新出的特性,用来处理异步编程的,其实我们要知道真正实现的异步操作还是Task新增线程来实现的,但是不代表说开一个Task,就开一个线程,有可能是几个Task在一个线程上运行的,他们并不是一一对应的关系,充分利用线程,下面的事例就已经能够说明这一点
Task创建
Task创建有两种方式一种通过任务工厂赋值立即运行,一种是直接实例化。下面这个例子创建了10个Task
static void Main(string[] args)
{
//启用线程池中的线程异步执行
Task t1 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Task1启动...线程ID:"+Thread.CurrentThread.ManagedThreadId);
});
Task t2 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Task2启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
});
//new 实例化启动
Task t3 = new Task(() =>
{
Console.WriteLine("Task3启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
});
t3.Start();
Task t4 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Task4启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
});
Task t5 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Task5启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
});
Task t6 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Task6启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
});
Task t7 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Task7启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
});
Task t8 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Task8启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
});
Task t9 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Task9启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
});
Task t10 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Task10启动...线程ID:" + Thread.CurrentThread.ManagedThreadId);
});
Console.ReadLine();
}
创建的10个Task,我们从结果中也证明了Task和线程并不是一一对应的关系,结果如图:
Task构造函数
Task状态
我们创建一个task,调用他的Start、Wait方法
static void Main(string[] args)
{
var task = new Task(()=> {
Console.WriteLine("Task创建成功");
});
Console.WriteLine("task未开始:"+task.Status);
task.Start();
Console.WriteLine("task已经开始:"+task.Status);
task.Wait();
Console.WriteLine("task已经等待:"+task.Status);
}
结果如图:
我们从图中可以知道,Task的生命周期如下:
Created:在已经实例化未Start之前的状态
WaittingToRun:表示等待分配线程给Task执行
RanToCompletion:任务执行完毕
Task等待任务结果
1.Task.WaitAll从这个字面意思就知道等待所有任务执行完成,和上面例子Wait方法等待一个任务执行完成很相似,我们来看一个代码:
var task1 = new Task(() =>
{
System.Threading.Thread.Sleep(3000);
Console.WriteLine("task1Created");
});
var task2 = new Task(() =>
{
System.Threading.Thread.Sleep(3000);
Console.WriteLine("task2Created");
});
task1.Start();
task2.Start();
Task.WaitAll(task1, task2);
Console.WriteLine("所有任务执行完!");
Console.Read();
结果输出:
task1Created
task2Created
所有任务执行完
除了WaitAll方法还有这些常用的方法
- Task.WaitAny:等待任何一个任务向下执行
- Task.ContinueWith等待第一个Task完成自动启动,触发下一个Task,也就是当做任务完成时触发的回调方法
- Task.GetAwaiter().OnCompleted(Action action) :GetAwaiter 方法获取任务的等待者,调用OnCompleted事件,任务完成时触发
Task任务取消
static void Main(string[] args)
{
var source = new CancellationTokenSource();
var token = source.Token;
Task t1 = Task.Run(() =>
{
Thread.Sleep(2000);
if (token.IsCancellationRequested)
{
Console.WriteLine("任务已取消");
}
Thread.Sleep(1000);
},token);
Console.WriteLine(t1.Status);
//取消任务
source.Cancel();
Console.WriteLine(t1.Status);
Console.ReadLine();
}
结果如图:
本文来自 张林blog 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/kebi007/article/details/76899078?utm_source=copy
C#异步编程基础入门总结的更多相关文章
- .Net Core WebAPI 基于Task的同步&异步编程快速入门
.Net Core WebAPI 基于Task的同步&异步编程快速入门 Task.Result async & await 总结 并行任务(Task)以及基于Task的异步编程(asy ...
- C#异步编程(一)线程及异步编程基础
最近试着做了几个.NET CORE的demo,看了些源码,感觉异步编程在Core里面已经成为主流,而对这块我还没有一个系统的总结,所以就出现了这篇文字,接下来几篇文章,我会总结下异步编程的思路,主要参 ...
- c语言编程基础入门必备知识
数据类型 基本数据类型 类型名称说明char字符类型存放字符的ASCII码int整型存放有符号整数short短整型存放有符号整数long长整型存放有符号整数long long存放有符号整数float单 ...
- 学习 Promise,掌握未来世界 JS 异步编程基础
其实想写 Promise 的使用已经很长时间了.一个是在实际编码的过程中经常用到,一个是确实有时候小伙伴们在使用时也会遇到一些问题.Promise 也确实是 ES6 中 对于写 JS 的方式,有着真正 ...
- C#并发编程-2 异步编程基础-Task
一 异步延迟 在异步方法中,如果需要让程序延迟等待一会后,继续往下执行,应使用Task.Delay()方法. //创建一个在指定的毫秒数后完成的任务. public static Task Delay ...
- 五.Bash Shell编程基础入门实战
知识回顾 运行脚本我们一般用sh 不用单独去加执行权限 OLDBOY=10只适用当前环境 局部变量 export OLDBOY把它设置为临时的环境变量应为已经=10了所以不用export OLDBOY ...
- Qt编程基础入门之二
QMainWindow 菜单栏 菜单栏 最多有一个 //菜单栏创建,一个 QMenuBar *menu = new QMenuBar(this); // this->setMenuBar(men ...
- Shell编程菜鸟基础入门笔记
Shell编程基础入门 1.shell格式:例 shell脚本开发习惯 1.指定解释器 #!/bin/bash 2.脚本开头加版权等信息如:#DATE:时间,#author(作者)#mail: ...
- .Net异步编程详解入门
前言 今天周五,早上起床晚了.赶着挤公交上班.但是目前眼前有这么几件事情.刷牙洗脸.泡牛奶.煎蛋.在同步编程眼中.先刷牙洗脸,然后烧水泡牛奶.再煎蛋,最后喝牛奶吃蛋.毫无疑问,在时间紧促的当下.它完了 ...
随机推荐
- windows环境下命令打到服务中
1.正常redis在本地命令行中启动,现在直接在服务中启动(tomcat同理) cmd下命令如下: sc create redis binPath= D:\redis\redis-server.exe ...
- 安装svn客户端后,代码不能提交
转载:https://jingyan.baidu.com/article/e8cdb32b3312f637052badde.html
- Ext JS 6 入门学习资料大全(2018-03-07)
现在 sencha touch已经升级为 Ext JS 6 了重新整理下资料 官方网站:https://www.sencha.com/ 在线文档:http://docs.sencha.com/extj ...
- vue+axios如何操作数据交互
参考: http://www.php.cn/js-tutorial-403543.html
- Nestjs 身份验证
文档 yarn add @nestjs/passport passport passport-http-bearer @nestjs/jwt passport-jwt auth.service.ts ...
- Web前端攻击方式及防御措施
一.XSS [Cross Site Script]跨站脚本攻击 恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户 ...
- SHELL编程之产生随机数
shell有一个环境变量RANDOM,范围是0-32767 如果想得到1-68范围内的数:$(($RANDOM%68+1)) 或者创建随机数函数: function rand() { min=$1 m ...
- 微信sdk 图片上传 两种方法 上传一张显示一张 并附带微信图片放大功能和删除功能
html <!--上传图片--> <div class="upload-mod"> <div class="up-box" id= ...
- day20:序列化模块,模块的导入
1,什么是序列化--将原本的字典,列表等内容转换成一个字符串的过程就叫做序列化,字符串是有顺序的,序列化转向一个字符串的过程,我们平时说的序列,指的就是字符串. 2,为何要序列化?本来字符串是可以强转 ...
- idea实用插件
代码规范检测插件: Alibaba Java Coding GuideLines使用@data插件lombok数据库mapper插件mybatisX前端运行vue的插件,装起了后在Terminal上运 ...