异步编程和线程的使用(.NET 4.5 )
C#:异步编程和线程的使用(.NET 4.5 )

异步编程和线程处理是并发或并行编程非常重要的功能特征。为了实现异步编程,可使用线程也可以不用。将异步与线程同时讲,将有助于我们更好的理解它们的特征。
本文中涉及关键知识点
1. 异步编程
2. 线程的使用
3. 基于任务的异步模式
4. 并行编程
5. 总结
异步编程
什么是异步操作?异步操作是指某些操作能够独立运行,不依赖主流程或主其他处理流程。通常情况下,C#程序从Main方法开始,当Main方法返回时结束。所有的操作都是按顺序执行的。执行操作是有序列的,一个操作必须等到其前面的操作完成才能够执行。如以下代码示例:
1: static void Main(string[] args)
2:
3: {
4:
5: DoTaskOne();
6:
7: DoTaskTwo();
8:
9: }
“DoTaskOne”方法结束后,DoTaskTwo()才能够执行。
异步编程中常用后台运行的方法体现,主调用线程不会被阻塞。调用后台运行的方法后,执行流程会立即返回到调用的线程并继续执行其他任务。后台运行方法通常是用线程或任务来实现。
在上面的例子中,在“DoTaskOne”方法调用成功后,如果“DoTaskOne”是异步调用,,执行流程立即返回到Main方法中,并继续执行“DoTaskTwo” 方法。
C#提供了Thread类创建线程实现异步编程,或者使用.NET提供的异步模式实现异步编程。.NET中提供了三种不同的异步模式:
1. 异步编程模型(APM)模式
2. 基于事件的异步模式(EAP)
3. 基于任务的异步模式(TAP)
前两种模型微软官方并不推荐使用,本文不再详细描述。我们将详细讨论基于任务的异步模式(TAP):
线程的使用
在.NET 4.5中引入了异步编程模式,大部分情况下都不需要我们手动创建线程。编译器已经替代了开发人员来完成这项工作。
创建新线程是非常耗时的。一般情况下,异步和并行编程使用 “基于任务的异步模式(TAP)”和“任务并行库(TPL)”就够了。如果需要控制线程的功能则需要使用其他模式。
TAP和TPL都是基于任务。一般来说任务是从线程池中调用线程( 线程池是.NET框架创建的和维护的线程集。如果我们使用任务,就不需要直接调用线程池。
任务可以在以下情况运行:
1. 在正在运行的线程中
2. 在新线程中
3. 从线程池中的某一线程中
4. 没有线程也可以运行
如果使用任务机制,开发人员就不必担心线程的创建或使用,.NET框架已经为我们解决了这一难题。
有时候需要控制线程,执行以下操作:
1. 设置线程名称
2. 设置线程优先级
3. 设置线程是前端或后端运行
我们可以使用线程类来创建线程。
使用Thread类创建线程
Thread类的构造函数接收委托类型的参数
1. ThreadStart:定义了返回值为空的方法,且不带参数的方法。
2. ParameterizedThreadStart:定义了返回值为空且有一个object类型的参数。
下面是一个简单的例子,使用 Start方法启动一个新线程:
1: static void Main(string[] args)
2:
3: {
4:
5: Thread thread = new Thread(DoTask);
6:
7: thread.Start();// Start DoTask method in a new thread
8:
9: //Do other tasks in main thread
10:
11: }
12:
13: static public void DoTask() {
14:
15: //do something in a new thread
16:
17: }
可以用Lamda表达式代替线程名称:
1: static void Main(string[] args)
2:
3: {
4:
5: Thread thread = new Thread(() => {
6:
7: //do something in a new thread
8:
9: });
10:
11: thread.Start();// Start a new thread
12:
13: //Do other tasks in main thread
14:
15: }
如果不需要引用变量,可如下直接启动线程:
1: static void Main(string[] args)
2:
3: {
4:
5: new Thread(() => {
6:
7: //do something in a new thread
8:
9: }).Start();// Start a new thread
10:
11: //Do other tasks in main thread
12:
13: }
但是,如果想控制线程对象,对线程设置一些属性,需要在线程创建后引用线程变量。如下可给线程对象的不同属性设值:
1: static void Main(string[] args)
2:
3: {
4:
5: Thread thread = new Thread(DoTask);
6:
7: thread.Name = "My new thread";// Asigning name to the thread
8:
9: thread.IsBackground = false;// Made the thread forground
10:
11: thread.Priority = ThreadPriority.AboveNormal;// Setting thread priority
12:
13: thread.Start();// Start DoTask method in a new thread
14:
15: //Do other task in main thread
16:
17: }
调用引用变量,可以执行一些操作如中止线程或通过调用join方法等待阻塞线程。
如果需要通过函数传值,可以给Start方法传值。由于该方法的参数为Object类型,因此需要强制转换类型。
1: static void Main(string[] args)
2:
3: {
4:
5: Thread thread = new Thread(DoTaskWithParm);
6:
7: thread.Start("Passing string");// Start DoTaskWithParm method in a new thread
8:
9: //Do other task in main thread
10:
11: }
12:
13: static public void DoTaskWithParm(object data)
14:
15: {
16:
17: //we need to cast the data to appropriate object
18:
19: }
“async”和“await”关键字
.NET框架引入了两个新的关键字来实现异步编程:“async”和“await”。使用 “await”的异步方法必须由“async”修饰符来声明方法。“await”关键字修饰调用异步方法。await 运算符应用于一个异步方法中的任务以挂起该方法的执行,直到等待任务完成.如下:
1: private async static void CallerWithAsync()// async modifier is used
2:
3: {
4:
5: string result = await GetSomethingAsync();// await is used before a method call. It suspends
6: //execution of CallerWithAsync() method and control returs to the calling thread that can
//perform other task.
7:
8: Console.WriteLine(result);
9: // this line would not be executed before GetSomethingAsync() //method completes
10:
11: }
而“ async ”修饰符只能用于返回值为Task类型或Void的方法。它不能用于主程序的切入点。
所有的方法之前不能使用await关键字,使用“await”关键字方法必须返回 “可等待”类型。以下属于“可等待”类型:
1. Task
2. Task<T>
3. 自定义“可等待”类型。
基于任务的异步模式
首先我们需要声明一个返回类型为Task或Task<T>的异步方法。可以通过以下几种方式创建任务:
1. Task.Factory.StartNew方法:在之前的.NET版本(在.NET 4中),是创建和启动任务的主要方法。
2. Task.Run或Task.Run <T>方法:从.NET 4.5这个方法已经被使用。此方法足以满足常见情况。
3. Task.FromResult方法:如果结果是已计算,就可以用这个方法来创建任务。
创建并等待一个任务
使用Task.Run <T>方法创建Task。该方法将特定工作按顺序排列在线程池中运行,并返回工作的任务句柄。需要以下步骤从同步方法中创建异步任务:
1. 假设下面方法是同步的,但需要一定的时间来完成:
1: static string Greeting(string name)
2:
3: {
4:
5: Thread.Sleep(3000);
6:
7: return string.Format("Hello, {0}", name);
8:
9: }
2. 要以异步方式访问此方法,必须以异步方式封装。命名为“GreetingAsync”。增加“Async”的后缀命名异步方法。
1: static Task<string> GreetingAsync(string name)
2:
3: {
4:
5: return Task.Run<string>(() =>
6:
7: {
8:
9: return Greeting(name);
10:
11: });
12:
13: }
3.现在,可通过使用的await关键字调用异步方法GreetingAsync
1: private async static void CallWithAsync()
2:
3: {
4:
5: //some other tasks
6:
7: string result = await GreetingAsync("Bulbul");
8:
9: //We can add multiple “await” in same “async” method
10:
11: //string result1 = await GreetingAsync(“Ahmed”);
12:
13: //string result2 = await GreetingAsync(“Every Body”);
14:
15: Console.WriteLine(result);
16:
17: }
当“CallWithAsync”方法被调用时,与常规的同步方法一样执行,直到遇到“await”的关键字。当它执行到 await的关键字会处理执行,并开始等待“GreetingAsync(” Bulbul “)” 方法被完成。同时,程序流将返回” CallWithAsync “方法的调用者,并继续执行调用者的任务。
当“GreetingAsync(" Bulbul ") 方法完成,“CallWithAsync”的方法恢复 “await关键字后的其他任务。在本实例中,将继续执行的代码“Console.WriteLine(result)”
4. 使用任务持续:Task类 “ContinueWith”的方法定义了Task完成后被调用的代码。
1: private static void CallWithContinuationTask()
2:
3: {
4:
5: Task<string> t1 = GreetingAsync("Bulbul");
6:
7: t1.ContinueWith(t =>
8:
9: {
10:
11: string result = t.Result;
12:
13: Console.WriteLine(result);
14:
15: });
16:
17: }
如果使用“ContinueWith”的方法就不需要使用“await“关键字,编译器会自动在合适的位置中添加“await”关键字。
等候多个异步方法。
看看下面的代码:
1: private async static void CallWithAsync()
2:
3: {
4:
5: string result = await GreetingAsync("Bulbul");
6:
7: string result1 = await GreetingAsync(“Ahmed”);
8:
9: Console.WriteLine(result);
10:
11: Console.WriteLine(result1);
12:
13: }
有两个正在等待调用函数序列。“GreetingAsync(” Ahmed “)” 会在完成第一个呼叫“GreetingAsync(” Bulbul “)” 之后启动。如果“result”和上面的代码“result1”是独立的,那么连续的“awiating”并不是一个好的做法。
在这种情况下,我们可以简化调用方法,不需要添加多个“await”关键字,只在一个地方添加await关键字,如下所示,这种情况下,该方法的调用都可以并行执行。
1: private async static void MultipleAsyncMethodsWithCombinators()
2:
3: {
4:
5: Task<string> t1 = GreetingAsync("Bulbul");
6:
7: Task<string> t2 = GreetingAsync("Ahmed");
8:
9: await Task.WhenAll(t1, t2);
10:
11: Console.WriteLine("Finished both methods.\n " +
12:
13: "Result 1: {0}\n Result 2: {1}", t1.Result, t2.Result);
14:
15: }
在这里,我们使用Task.WhenAll连接器。Task.WhenAll创建一个任务,将完成所有的提供的任务。Task类也有其他的结合器。Task.WhenAny,当所任务链中所有的任务完成时,结束使用。
处理异常
必须把“await的代码块放在try块内捕获异常。
1: private async static void CallWithAsync()
2:
3: {
4:
5: try
6:
7: {
8:
9: string result = await GreetingAsync("Bulbul");
10:
11: }
12:
13: catch (Exception ex)
14:
15: {
16:
17: Console.WriteLine(“handled {0}”, ex.Message);
18:
19: }
20:
21: }
如果try块中有多个“await”,只有第一个” await“异常会被处理,其他“await”将无法被捕捉。如果希望所有的方法都能捕获异常,不能使用“await”关键字调用方法,使用Task.WhenAll来执行任务。
1: private async static void CallWithAsync()
2:
3: {
4:
5: try
6:
7: {
8:
9: Task<string> t1 = GreetingAsync("Bulbul");
10:
11: Task<string> t2 = GreetingAsync("Ahmed");
12:
13: await Task.WhenAll(t1, t2);
14:
15: }
16:
17: catch (Exception ex)
18:
19: {
20:
21: Console.WriteLine(“handled {0}”, ex.Message);
22:
23: }
24:
25: }
捕获所有任务的错误一种方法是在try块之外声明任务,这样可以从try块进行访问,并检查任务的“IsFaulted”属性。如果它存在异常那么“IsFaulted”属性值为True,就可捕获任务实例的内部异常。
还有另一个更好的办法:
1: static async void ShowAggregatedException()
2:
3: {
4:
5: Task taskResult = null;
6:
7: try
8:
9: {
10: Task<string> t1 = GreetingAsync("Bulbul");
11:
12: Task<string> t2 = GreetingAsync("Ahmed");
13:
14: await (taskResult = Task.WhenAll(t1, t2));
15:
16: }
17:
18: catch (Exception ex)
19:
20: {
21:
22: Console.WriteLine("handled {0}", ex.Message);
23:
24: foreach (var innerEx in taskResult.Exception.InnerExceptions)
25:
26: {
27: Console.WriteLine("inner exception {0}", nnerEx.Message); }
28: }
29:
30: }
取消任务
在此之前,如果从线程池中调用线程,线程是不可能取消。现在,Task类提供了一个方法基于CancellationTokenSource类能够取消已启动的任务,取消任务步骤:
1. 异步方法应该除外 “ CancellationToken” 参数类型
2. 创建CancellationTokenSource类实例:
var cts =new CancellationTokenSource();
3. 传递CancellationToken,如:
1: Task<string> t1 = GreetingAsync("Bulbul", cts.Token);
4. 长时间运行的方法中,必须调用CancellationToken 的ThrowIfCancellationRequested()方法。
1: static string Greeting(string name, CancellationToken token){
2:
3: Thread.Sleep(3000);
4:
5: token. ThrowIfCancellationRequested();
6:
7: return string.Format("Hello, {0}", name);
8:
9: }
5. 从等待的Task中捕获 OperationCanceledException异常。
6. 如果通过调用CancellationTokenSource的实例的方法执行取消操作,将从长时间运行操作中抛出OperationCanceledException异常。也可以设置取消的时间。以下是完整的代码,一秒后执行取消操作:
1: static void Main(string[] args)
2:
3: {
4: CallWithAsync();
5:
6: Console.ReadKey();
7:
8: }
9:
10:
11: async static void CallWithAsync()
12:
13: {
14:
15: try
16:
17: {
18:
19: CancellationTokenSource source = new CancellationTokenSource();
20:
21: source.CancelAfter(TimeSpan.FromSeconds(1));
22:
23: var t1 = await GreetingAsync("Bulbul", source.Token);
24: }
25:
26: catch (OperationCanceledException ex)
27:
28: {
29:
30: Console.WriteLine(ex.Message);
31:
32: }
33:
34: }
35:
36: static Task<string> GreetingAsync(string name, CancellationToken token)
37:
38: {
39:
40: return Task.Run<string>(() =>
41:
42: {
43:
44: return Greeting(name, token);
45:
46: });
47: }
48:
49:
50: static string Greeting(string name, CancellationToken token)
51:
52: {
53:
54: Thread.Sleep(3000);
55: token.ThrowIfCancellationRequested();
56:
57: return string.Format("Hello, {0}", name);
58:
59: }
60:
并行编程
.NET 4.5及以上版本推出“Parallel类,是线程类的抽象。使用“Parallel”类,我们可以实现并行。并行与线程不同,它使用所有可用的CPU或内核的。以下两种类型的并行是可行:
- 数据并行:如果我们有数据的大集合,我们希望在每个数据的某些操作进行并行使用,那么就可以使用数据并行。Parallel类有静态For或ForEach来执行数据并行行,如
1: ParallelLoopResult result =
2: Parallel.For(0, 100, async (int i) =>
3: {
4: Console.WriteLine("{0}, task: {1}, thread: {2}", i,
5: Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
6: await Task.Delay(10);
7:
8: });
For或ForEach方法可以在多线程中和且索引无序可以是无序的。
如果想停止并行For或ForEach方法,可通过ParallelLoopState作为参数,并根据需要打破循环的状态,跳出循环。
1: ParallelLoopResult result =
2: Parallel.For(0, 100, async (int i, ParallelLoopState pls) =>
3: {
4: Console.WriteLine("{0}, task: {1}, thread: {2}", i,
5: Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
6: await Task.Delay(10);
7: if (i > 5) pls.Break();
8: });
2. 任务并行:如果想要同时运行多个任务的,我们可以通过调用Parallel类的invoke方法使用任务并行Parallel.Invoke方法接收委托行为的数组。例如:
1: static void ParallelInvoke()
2:
3: {
4:
5: Parallel.Invoke(MethodOne, MethodTwo);
6:
7: }
8:
结论
本文详细介绍了.NET Framework 4.5提供的异步编程技术及细节。
原文链接:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N
异步编程和线程的使用(.NET 4.5 )的更多相关文章
- C#:异步编程和线程的使用(.NET 4.5 )
摘自:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N(葡萄城 ...
- C#:异步编程和线程的使用(.NET 4.5 ),异步方法改为同步执行
摘自:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N(葡萄城 ...
- C#异步编程の----Threadpool( 线程池)
简介: 一个托管线程的创建需要数千个CPU周期,并且当发生线程切换时也会带来明显的开销.考虑线程的重用,避免不断重复创建新的线程是提高系统效率的一种方式. 线程池是一种提供效率的方式,它创建好一些线程 ...
- .NET面试题解析(07)-多线程编程与线程同步
系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等, ...
- .NET面试题解析(07)-多线程编程与线程同步 (转)
http://www.cnblogs.com/anding/p/5301754.html 系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实 ...
- C#实现异步编程的两个简单机制(异步委托&定时器)及Thread实现多线程
创建线程的常用方法:异步委托.定时器.Thread类 理解程序.进程.线程三者之间的区别:简而言之,一个程序至少有一个进程,一个进程至少有一个线程进程就是在内存中运行的程序(即运行着的程序):一个进程 ...
- Java8函数之旅 (八) - 组合式异步编程
前言 随着多核处理器的出现,如何轻松高效的进行异步编程变得愈发重要,我们看看在java8之前,使用java语言完成异步编程有哪些方案. JAVA8之前的异步编程 继承Thead类,重写run方法 实现 ...
- 【憩园】C#并发编程之异步编程(二)
写在前面 前面一篇文章介绍了异步编程的基本内容,同时也简要说明了async和await的一些用法.本篇文章将对async和await这两个关键字进行深入探讨,研究其中的运行机制,实现编码效率与运行效率 ...
- C#并发编程之异步编程2
C#并发编程之异步编程(二) 写在前面 前面一篇文章介绍了异步编程的基本内容,同时也简要说明了async和await的一些用法.本篇文章将对async和await这两个关键字进行深入探讨,研究其中 ...
随机推荐
- Cocos发展Visual Studio下一个libcurl图书馆开发环境的搭建
我们解释win32在Visual Studio下一个libcurl图书馆开发环境的搭建.Cocos2d-x发动机实际上与Win32在访问libcurl库.Cocos2d-x 3.x在libcurl库文 ...
- 同台电脑部署多组Tomcat负载均衡(或集群)
可能这种需求比较少见,不过如果手上服务器不够用.可以考虑先这么干着.. 具体Tomcat怎么搭集群,就不在这细说了.只写同台电脑部署多组集群需要修改和注意的地方. 一.Apache 先是Apache, ...
- 【C++探索之旅】第一部分第二课:C++编程的必要软件
内容简介 1.第一部分第二课:C++编程的必要软件 2.第一部分第三课预告:第一个C++程序 C++编程的必要软件 经过上一课之后,大家是不是摩拳擦掌,准备大干一场了呢. 这一课我们来做一些C++开发 ...
- iis6开户gzip 网站属性里面没有服务选项卡
请注意一点,是直接在名为“网站”的文件夹上面右键选择属性,不是去点下面建立的某一个网站.开户GZIP是整台服务器上面的虚拟主机都同时开启的,不对针某一个单独网站. 开启Gzip具体步骤: 1. 在 & ...
- 构建安全的Xml Web Service系列之wse之证书存储位置
原文:构建安全的Xml Web Service系列之wse之证书存储位置 我们在前几天对xml web service的安全性提出了一些建议,大家可以通过以下地址访问: 构建安全的Xml Web Se ...
- hibernate它 10.many2many单向
在前文hibernate之5.many2one单向提到多对多关系,表结构设计是基于中间表来实现, 以下以用户与角色(多对多)为例,在Hibernate是怎样操作的 表结构设计: 类图: CRUD; S ...
- hdu 2767 Proving Equivalences 强连通缩点
给出n个命题,m个推导,问最少添加多少条推导,能够使全部命题都能等价(两两都能互推) 既给出有向图,最少加多少边,使得原图变成强连通. 首先强连通缩点,对于新图,每一个点都至少要有一条出去的边和一条进 ...
- ProgressMonitorInputStream
Swing类包中有一个很有用的流过滤器,ProgressMonitorInputStream,它可以自动弹出一个对话框,监视已经读取了多少流. 进度监视器流使用InputStream类的availab ...
- 大约linux的几个问题,你能回答几个?--回复14-20称号
14.select和poll差异?Poll和epoll的差别? (1)select和poll的差别:(參考:http://blog.csdn.net/mituan2008/article/detail ...
- 具体的了解“>/dev/null 2>&1”
Linux系统中不管是crontab里面.还是平时使用的命令.常常会碰到">/dev/null 2>&1".比方说:在Crontab Job里面,假设不想发送邮 ...