龙空技术网

C#异步编程之Task的使用

码农小灰灰 368

前言:

现在同学们对“c语言异步调用怎么实现”大体比较重视,朋友们都需要了解一些“c语言异步调用怎么实现”的相关文章。那么小编在网络上汇集了一些有关“c语言异步调用怎么实现””的相关资讯,希望兄弟们能喜欢,姐妹们一起来了解一下吧!

1.新旧异步编程对比

namespace ConsoleApp2{ class Program { static void Main(string[] args) { TestClass class1 = new TestClass(); class1.TestOldAsycn(); class1.TestNewAsycn(); Console.ReadLine(); } } public class TestClass { private delegate long AddDel(int i, int j); //--------------旧的用BeginXXX, EndXXX, 或者xx.xxxAsycn----------------- public void TestOldAsycn() { AddDel del = Add; IAsyncResult ar = del.BeginInvoke(2, 3, MyCallBack, del);//参数里面的del 是用于传给回调函数调用,参看回调函数方法里的代码: ar.AsyncState as AddDel; //var result del.EndInvoke(ar);//获取结果,会阻塞线程 Console.WriteLine("Main contine ......."); } private long Add(int i, int j) { Console.WriteLine("begin wait 5 sec---------------"); System.Threading.Thread.Sleep(5000); return i + j; } /// <summary> /// callback 获得结果回到函数,beginXXX..里的任务执行完成后才会调用这个 /// </summary> /// <param name="ar"></param> private void MyCallBack(IAsyncResult ar) { var d = ar.AsyncState as AddDel;//ar.AsyncState 是bingXXX里的Object var result = (d.EndInvoke(ar));//执行EndXXX获得结果 Console.WriteLine("result:" + result); } //--------------新的.net更方便的Task/Asyc/Await语法------------------ public async Task TestNewAsycn() { var result = await AddNew(2, 3); // OR AddNew(2,3); Console.WriteLine("new asycn result:" + result); } private async Task<long> AddNew(int i, int j) { Console.WriteLine("begin wait 5 sec---------------"); System.Threading.Thread.Sleep(5000); return await Task.FromResult(i + j); } }}

2.Task 、Thread 以及Thread Pool区别

特点

缺点

优点

形象

Thread

一次使用一个线程,用完后删除

创建删除代价昂贵

可以设置执行先级等,可控性更好

游乐场的临时人工移动窗口,当日用当日搬来,

Threadpool

ThreadPool类会在线程的托管池中重用已有的线程。使用完线程后,线程就会返回线程池,供以后使用。

1.ThreadPool不支持线程的取消、完成、失败通知等交互性操作

2. ThreadPool不支持线程执行的先后次序

反应快,减少创建和删除的开销,性能比Thead好

游乐场的无人固定售卖窗口,一致在哪里,数量固定,可以排队;本身没有执行优先级别,无法沟通

Task

和外部的交互性很好,同时支持完成通知、取消、获取任务返回值等功能

1.同时支持完成通知、取消、获取任务返回值等功能

2.多核CPU性能更佳

总结:Thread 就是线程,一个小小的对象而已,线程上可以执行一个函数,主要用法可以用来并发执行一些动作,也能在不阻塞UI的情况下完成一些持续计算。但是很多人觉得每次调用一个函数都要new一个线程是很麻烦的,所以干脆提前new好了很多线程装在一个list中,你要调用函数的时候就从list中提取出一个空闲的线程,函数执行完毕后,就把这个空闲的线程又放到这个list中,减少了new thread的时间,所以线程池说白了就是List,提供一个方法,让你能方便的把自己的函数不管三七二十一都放这个list中去,然后依次执行。所以,如果你常常使用系统的线程池,你甚至不需要知道Thread是什么东西,你只要知道这是一个魔术口袋,你把你的函数塞进去,过一阵子就执行完了,根本不要你来操心。后来大家发现线程池也不方便,因为进了这个魔术口袋的函数,你不能突然中断它的执行。在多核时代,它的效率也不尽如人意。所以微软又把原来的线程池改造了一下,现在都不叫threadPool了,直接叫Task,你不必管我是怎么实现的,你只要把函数塞我肚肚里,我一定会执行,而且你能用我提供的API来控制整个过程,所以Task就是一个方便使用的线程池。至于把函数塞进去。肯定是在其它线程中执行的,只是这不是我们需要操心的了。

3.System.Threading.Tasks.Task简介

Task的只读属性:

返回值

名称

说明

object

AsyncState

表示在创建任务时传递给该任务的状态数据

TaskCreationOptions

CreationOptions

获取用于创建此任务的 TaskCreationOptions

CurrentId

当前正在执行 Task 的 ID

AggregateException

Exception

获取导致 AggregateException 提前结束的 Task。如果 Task 成功完成或尚未引发任何异常,则返回 null

TaskFactory

Factory

提供对用于创建 Task 和 Task<TResult> 的工厂方法的访问

int

Id

获取此 Task 实例的 ID

bool

IsCanceled

指明此 Task 实例是否由于被取消的原因而已完成执行

bool

IsCompleted

指明此 Task 是否已完成。注意,当Task处于RunToCompletion,Canceled或者Faulted状态时(即三种最终状态),IsCompleted返回True。所以为了判断一个Task是否成功完成,最简单的方法是:if(task.Status == TaskStatus.RanToCompletion)

bool

IsFaulted

指明Task 是否由于未经处理异常的原因而完成

TaskStatus

Status

获取此任务的 TaskStatus

4.Task状态和生命周期

一个Task实例只会完成其生命周期一次,当Task达到它的3种可能的最终状态之一时,它就再也回不去之前的状态了。任务的生命周期从TaskStatus.Created状态真正开始。

首次构造一个Task对象时,他的状态是Created。当任务启动时,他的状态变成WaitingToRun。Task在一个线程上运行时,他的状态变成Running。任务停止运行,等待他的任何子任务时,状态变成WaitingForChildrenToComplete。任务完全结束时,它进入以下三个状态之一:RanToCompletion,Canceled或者Faulted。初始状态

Task实例有三种可能的初始状态

说明

TaskStatus.Created

该任务已初始化,但尚未被计划。使用Task构造函数创建Task实例时的初始状态。

TaskStatus.WaitingForActivation

该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。一个任务的初始状态,这个任务只有当其依赖的任务完成之后才会被调度。

TaskStatus.WaitingToRun

该任务已被计划执行,但尚未开始执行。使用TaskFactory.StartNew创建的任务的初始状态。

中间状态

Task实例有两种可能的中间状态

说明

TaskStatus.Running

该任务正在运行,但尚未完成

TaskStatus.WaitingForChildrenToComplete

该任务已完成执行,正在隐式等待附加的子任务完成

最终状态

Task实例有三种可能的最终状态

说明

TaskStatus.Canceled

该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态;或者在该任务开始执行之前,已向该任务的 CancellationToken 发出了信号。Task属性IsFaulted被设置为true

TaskStatus.Faulted

由于未处理异常的原因而完成的任务。Task属性IsCanceled被设置为true

TaskStatus.RunToCompletion

已成功完成执行的任务。Task属性IsCompleted被设置为true,IsFaulted和IsCanceled被设置为false

5.Task的创建

static void Main(string[] args){ //1.new方式实例化一个Task,需要通过Start方法启动 Task task = new Task(() => { Thread.Sleep(100); Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}"); }); task.Start(); //2.Task.Factory.StartNew(Action action)创建和启动一个Task Task task2 = Task.Factory.StartNew(() => { Thread.Sleep(100); Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}"); }); //3.Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task Task task3 = Task.Run(() => { Thread.Sleep(100); Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}"); }); Console.WriteLine("执行主线程!"); Console.ReadKey();}

Task.Factory.StartNew与Task.Run的区别:Task.Factory.StartNew重载方法提供更多的参数,可以控制如何计划执行任务以及如何向调试器公开计划任务的机制和控制任务的创建和执行的可选行为,而Task.Run提供的方法则不具有上述控制机制。

5.1.等待任务完成public void Wait(),实例方法:等待 System.Threading.Tasks.Task 完成执行过程public static void WaitAll(params Task[] tasks),静态方法:等待所有传入的task完成。public static int WaitAny(params Task[] tasks),静态方法:等待传入的task任何一个完成,返回完成的task的索引。5.2.Task的取消

Task的取消涉及两个对象,一个是System.Threading.CancellationToken,传播有关应取消操作的通知,在创建Task时传入。另一个是System.Threading.CancellationTokenSource,通知 System.Threading.CancellationToken,告知其应被取消。创建一个取消的Task一般要进行下面一些步骤:

//a.创建System.Threading.CancellationTokenSource的一个实例CancellationTokenSource tokenSource = new CancellationTokenSource();//b.通过CancellationTokenSource.Token属性获得一个System.Threading.CancellationTokenCancellationToken token = tokenSource.Token;//c.创建一个新的Task或者Task<T>,并且在构造函数传入Action或者Action<object>的委托作为第一个参数,传入CancellationToken作为第二个参数Task task = new Task(new Action(printMessage), token);//d.调用Task的Start()方法task.Start();

上面的步骤和我们之前介绍的创建一个Task的代码几乎一样,只是在构造函数中多传入了一个参数(其实这个CancellationToken就是.NET Framework用来避免我们再次运行已经被取消的Task,可以说就是一个标志位)。如果想要取消一个Task的运行,只要调用CancellationToken实例的Cancel()方法就可以了。有点要特别注意的,当我们调用了Cancel()方法之后,.NET Framework不会强制性的去关闭运行的Task。我们自己必须去检测之前在创建Task时候传入的那个CancellationToken。

通过轮询的方式检测Task是否被取消

while (true){ if (token.IsCancellationRequested) { //释放资源 throw new OperationCanceledException(token); } else { // do a unit of work }}//如果我们没有任何的资源要释放,那么只要简单的调用CancellationToken.ThrowIfCancellationRequested()方法,这个方法会检查是否要取消task,并且抛出异常while (true){ token.ThrowIfCancellationRequested();//在主线程没有调用CancellationToken.Cancel()前,这个函数是不会触发的,一旦Cancel被调用,task将会抛出OperationCanceledException来中断此任务的执行 //do a unit of work}
用委托delegate来检测Task是否被取消

我们可以在调用CancellationToken.Cancel()前注册一个委托到CancellationToken中,这个委托的方法在CancellationToken.Cancel()调用时被触发。在这个委托的方法中我们可以做很多的事情,如通知用户取消操作发生了

static void Main(string[] args){ // create the cancellation token source CancellationTokenSource tokenSource = new CancellationTokenSource(); // create the cancellation token CancellationToken token = tokenSource.Token; // create the task Task task = new Task(() => { for (int i = 0; i < int.MaxValue; i++) { if (token.IsCancellationRequested) { Console.WriteLine("Task cancel detected"); throw new OperationCanceledException(token); } else { Console.WriteLine("Int value {0}", i); } } }, token); // register a cancellation delegate token.Register(() => { Console.WriteLine(">>>>>> Delegate Invoked\n"); }); // wait for input before we start the task Console.WriteLine("Press enter to start task"); Console.WriteLine("Press enter again to cancel task"); Console.ReadLine(); // start the task task.Start(); // read a line from the console. Console.ReadLine(); // cancel the task Console.WriteLine("Cancelling task"); tokenSource.Cancel(); // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine();}

5.3Task的返回值

Task<Result>是Task的子类,创建Task时参数是Action委托,创建Task<Result>时参数是Func委托,返回值类型为Result。对于Task<Result>,可以通过其Result属性获取返回值。

class Program { static void Main(string[] args) { Task<int> task = Task.Run(() => { int total = 0; for (int i = 0; i <= 100; i++) { total += i; } Thread.Sleep(2000); return total; }); int totalCount = task.Result;//如果任务没有完成,则阻塞(Reulst属性内部会调用Wait等待) Console.ReadKey(); } }

5.4.Task异常

与线程不同,任务可以随时抛出异常。所以,如果任务中的代码抛出一个未处理异常,那么这个异常会自动传递到调用Wait()或Task<TResult>的Result属性的代码上。任务的异常将会自动捕获并抛给调用者,为确保报告所有的异常,CLR会将异常封装在AggregateException容器中,该容器公开的InnerExceptions属性中包含所有捕获的异常,从而更适合并行编程。

通过Wait/WaitAll方法捕获线程中的异常(线程阻塞方式)

class Program{ static void Main(string[] args) { try { Task.Run(() => { throw new Exception("错误"); }).Wait(); } catch (AggregateException axe) { foreach (var item in axe.InnerExceptions) { Console.WriteLine(item.Message);//控制台会显示:错误 } } Console.ReadKey(); }}

通过Wait/WaitAll的方式可以对线程异常进行捕获,但此时线程的执行会阻塞,不会再有异步的效果。

通过ContinueWith设置TaskContinuationOptions参数来捕获异常(线程异步方式)

class Program{ static void Main(string[] args) { try { Task t= Task.Run(() => { Thread.Sleep(3000); throw new Exception("错误"); }); //OnlyOnFaulted表示如果有异常,才会执行ContinueWith内部的代码,但此时线程不会被阻塞 t.ContinueWith(r => { string Exception = Convert.ToString(t.Exception); Console.WriteLine("异常信息2:" + Exception); }, TaskContinuationOptions.OnlyOnFaulted); Console.WriteLine("继续执行主线程"); } catch (AggregateException axe) { foreach (var item in axe.InnerExceptions) { Console.WriteLine(item.Message);//控制台会显示:错误 } } Console.ReadKey(); }}

将ContinueWith的第二个参数设置成TaskContinuationOptions.OnlyOnFaulted,此时ContinueWith内部的代码只有在Task出现异常后才会执行,如果Task没有异常,则ContinueWith内部的方法不会执行。因此我们只需要在ContinueWith内部写好异常处理方法就行了。

通过过t.Exception在线程执行完之后去判断异常信息

如果希望程序保持异步执行,而非阻塞等待,我们可以在一个线程执行一段时间后,通过对线程的返回结果进行判断,从而去获取异常信息。

我们可以先通过IsCompleted == true去判断线程运行是否结束如果线程结束后我们可以通过t.Exception != null去判断线程执行过程中是否出现异常如果有异常我们可以通过t.Exception.InnerExceptions.Count去判断异常数量最后t.Exception.InnerException.Message获取异常信息即可

也可以通过IsFaulted和IsCanceled属性:

IsFaulted和IsCanceled都返回False,表示没有错误发生。IsCanceled为True,则任务抛出了OperationCanceledOperation(取消线程正在执行的操作时在线程中抛出的异常)。IsFaulted为True,则任务抛出另一种异常,而Exception属性包含了该错误。

标签: #c语言异步调用怎么实现 #c begin