先从最简单的开始。test1()用以另一条Thread执行Thread.Sleep()及Console.WriteLine(),效果与ThreadPool.QueueUserWorkItem()相当。
排版显示纯文字
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; using System.Diagnostics;
namespace TaskLab { class Program { static void Main( string [] args) { test1(); Console.Read(); }
static void test1()
{
//Task可以代替TheadPool.QueueUserWorkItem使用
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine( "Done!" );
});
Console.WriteLine( "Async Run..." );
}
}
} StartNew()完会立刻执行下一行,故会先看到Aync Run,1秒后印出Done。
Async Run...Done! 同时启动数个作业多工并行,但要等待各作业完成再继续下一步是常见的应用情境,传统上可透过WaitHandle、AutoResetEvent、ManualResetEvent等机制实现;Task的写法相对简单,建立多个Task物件,再当成Task.WaitAny()或Task.WaitAll()的参数就搞定啰!
排版显示纯文字
static void test2()
{
var task1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
Console.WriteLine( "Done!(3s)" );
});
var task2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
Console.WriteLine( "Done!(5s)" );
});
//等待任一作业完成后继续
Task.WaitAny(task1, task2);
Console.WriteLine( "WaitAny Passed" );
//等待两项作业都完成才会继续执行
Task.WaitAll(task1, task2);
Console.WriteLine( "WaitAll Passed" );
}
task1耗时3秒、task2耗时5秒,所以3秒后WaitAny()执行完成、5秒后WaitAll()执行完毕。
Done!(3s)WaitAny PassedDone!(5s)WaitAll Passed 如果要等待多工作业传回结果,透过StartNew<T>()指定传回型别建立作业,随后以Task.Result取值,不用额外写Code就能确保多工作业执行完成后才读取结果继续运算。
排版显示纯文字
static void test3()
{
var task = Task.Factory.StartNew< string >(() =>
{
Thread.Sleep(2000);
return "Done!" ;
});
//使用马表计时
Stopwatch sw = new Stopwatch();
sw.Start();
//读task.Result时,会等到作业完毕传回值后才继续
Console.WriteLine( "{0}" , task.Result);
sw.Stop();
//要取得task.Result耗时约2秒
Console.WriteLine( "Duration: {0:N0}ms" , sw.ElapsedMilliseconds);
}
实际执行,要花两秒才能跑完Console.WriteLine("{0}", task.Result),其长度就是Task执行并传回结果的时间。
Done!Duration: 2,046ms 如果要安排多工作业完成后接连执行另一段程式,可使用ContinueWith():
排版显示纯文字
static void test4()
{
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine( "Done!" );
}).ContinueWith(task =>
{
//ContinueWith会等待前项工作完成才执行
Console.WriteLine( "In ContinueWith" );
});
Console.WriteLine( "Async Run..." );
}
如预期,ContinueWith()里的程式会在Task完成后才被执行。
Async Run...Done!In ContinueWith .ContinueWith()传回值仍是Task物件,所以我们可以跟jQuery一样玩接接乐,在ContinueWith()后方再接上另一个ContinueWith(),各段逻辑便会依顺序执行。
排版显示纯文字
static void test5()
{
//ContinueWith()可以串接
Task.Factory.StartNew(() =>
{
Thread.Sleep(2000);
Console.WriteLine( "{0:mm:ss}-Done" , DateTime.Now);
})
.ContinueWith(task =>
{
Console.WriteLine( "{0:mm:ss}-ContinueWith 1" , DateTime.Now);
Thread.Sleep(2000);
})
.ContinueWith(task =>
{
Console.WriteLine( "{0:mm:ss}-ContinueWith 2" , DateTime.Now);
});
Console.WriteLine( "{0:mm:ss}-Async Run..." , DateTime.Now);
}
Task耗时两秒,第一个ContinueWith()耗时2秒,最后一个ContinueWith()接续在4秒后执行。
59:13-Async Run...59:15-Done59:15-ContinueWith 159:17-ContinueWith 2 最后一个例子比较复杂。ContinueWith()中的Action<Task>都会有一个输入参数,借以得知前一Task的执行状态,有IsCompleted, IsCanceled, IsFaulted几个属性可用。
要取消执行,得借助CancellationTokenSource及其所属CancellationToken类别,做法是在Task中持续呼叫CancellationToken.ThrowIfCancellationRequested(),一旦外部呼叫CancellationTokenSource.Cancel(),便会触发OperationCanceledException,Task有机制侦测此种例外状况,将结束作业执行后续的ContinueWith(),并指定Task.IsCanceled为True以为识别;而当Task程式发生Exception,也会结束作业触发ContinueWith(),此时则Task.IsFaulted为True,ContinueWith()中可透过Task.Exception.InnerExceptions取得错误细节。
以下程式同时可测试Task正常、取消及错误三种情境,使用者透过输入1,2或3来决定要测试哪一种。在Task外先宣告一个CancellationTokenSource类别,将其中的Token属性当成StartNew()的第二项参数,而Task中则保留最初的五秒可以取消,方法是每隔一秒呼叫一次CancellationToken.ThrowIfCancellationRequested(),当程式外部呼叫CancellationTokenSource.Cancel(),Task就会结束。5秒后若未取消,再依使用者决定的测试情境return结果或是抛出Exception。ContinueWith()则会检查IsCanceled, IsFaulted等旗标,并输出结果。
排版显示纯文字
static void test6() { CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken cancelToken = cts.Token; Console.Write( "Test Option 1, 2 or 3 (1-Complete / 2-Cancel / 3-Fault) : " ); var key = Console.ReadKey(); Console.WriteLine(); Task.Factory.StartNew< string >(() => { //保留5秒侦测是否要Cancel for (var i = 0; i < 5; i++) { Thread.Sleep(1000); //如cancelToken.IsCancellationRequested //抛出OperationCanceledException cancelToken.ThrowIfCancellationRequested(); } switch (key.Key) { case ConsoleKey.D1: //选1时 return "OK" ; case ConsoleKey.D3: //选2时 throw new ApplicationException( "MyException" ); } return "Unknown Input" ; }, cancelToken).ContinueWith(task => { Console.WriteLine( "IsCompleted: {0} IsCanceled: {1} IsFaulted: {2}" , task.IsCompleted, task.IsCanceled, task.IsFaulted); if (task.IsCanceled) { Console.WriteLine( "Canceled!" ); } else if (task.IsFaulted) { Console.WriteLine( "Faulted!" ); foreach (Exception e in task.Exception.Flattern().InnerExceptions) { Console.WriteLine( "Error: {0}" , e.Message); } } else if (task.IsCompleted) { Console.WriteLine( "Completed! Result={0}" , task.Result); } }); Console.WriteLine( "Async Run..." ); //如果要测Cancel,2秒后触发CancellationTokenSource.Cancel if (key.Key == ConsoleKey.D2) { Thread.Sleep(2000); cts.Cancel(); } } 以下是三种测试情境的结果。
正常执行:
Test Option 1, 2 or 3 (1-Complete / 2-Cancel / 3-Fault) : 1Async Run...IsCompleted: True IsCanceled: False IsFaulted: FalseCompleted! Result=OK 取消: (IsCanceled为True,但留意IsCompleted也算True)
Test Option 1, 2 or 3 (1-Complete / 2-Cancel / 3-Fault) : 2Async Run...IsCompleted: True IsCanceled: True IsFaulted: FalseCanceled! 错误: (IsFaulted为True,IsCompleted也是True)
Test Option 1, 2 or 3 (1-Complete / 2-Cancel / 3-Fault) : 3Async Run...IsCompleted: True IsCanceled: False IsFaulted: TrueFaulted!Error: MyException 【小结】
说穿了,Task能做的事,过去使用Thread/ThreadPool配合Event、WaitHandle一样能办到,但使用Task能以较简洁的语法完成相同工作,使用.NET 4.0开发多工作业程式应可多加利用。同时,Task也是.NET 4.5 async await的基础概念之一,值得大家花点时间熟悉,有益无害。
本文摘抄于网络,出处http://blog.darkthread.net/post-2012-07-20-net4-task.aspx