虽然今天的重点是.NET4.5的async/await设计模式可是因为不少人对于.NET4.0中的Task仍然仍是没有接触过,Task也是.NET 4.5 async await的基础概念之一,值得你们花点时间熟悉,那么这里就将他也做为一个附加专题来作一下讲解。 web
到咱们在开发SignalR程序的时候,就必需要使用到多线程,假设没有.NET4.5的支持,那么你可能想到的最简单方式就是使用Task,它取代了传统的Thread,TheadPool的写法,能大幅度的简化同步逻辑的写法,颇为便利,下面咱们来看几个典型的范例。 设计模式
Test1()用以另外一Thread执行Thread.Sleep()及Console.WriteLine(),效果与ThreadPool.QueueUserWorkItem()至关。多线程
private static void Test1() { //Task能够代替TheadPool.QueueUserWorkItem使用 Task.Factory.StartNew(() => { Thread.Sleep(2000); Console.WriteLine("Done!"); }); Console.WriteLine("Async Run..."); }
StartNew()完会马上执行下一行,故会先看到Aync Run,1秒后打印出Done。架构
同时启动多个做业多工并行(多线程并行),但要等待各做业完成再继续下一步的应用场境传统方式上可经过WaitHandle、AutoResetEvent、ManualResetEvent等机制实现;Task的写法至关简单,创建多個Task对象,再做为Task.WaitAny()或Task.WaitAll()的参数就搞定了! 异步
private 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, task1); Console.WriteLine("WaitAny Passed"); //等待全部做业完成后继续 Task.WaitAll(task1, task2); Console.WriteLine("WaitAll Passed"); }
task1耗时3秒、task2耗时5秒,因此3秒后WaitAny()执行完成、5秒后WaitAll()执行完毕。async
经过StartNew<T>()指定传回类型创建做业,随后以Task.Result取值,不用额外Code就能确保多工做业执行完成后才读取结果继续运行 测试
private 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執行并回传结果的时间。 大数据
private 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完成后才被执行。
.ContinueWith()传回值还是Task对象,因此咱们能够跟jQuery同样连连看,在ContinueWith()後方再接上另外一个ContinueWith(),各段逻辑便会依顺序执行。 this
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秒后执行。 spa
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等标识,并输出结果。
private static void Test6() { CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken cancelToken = cts.Token;//获取与此CancellationTokenSource关联的CancellationToken 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: //选3时 throw new ApplicationException("MyFaultException"); } 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.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(); } }
Task能作的事,过去使用Thread/ThreadPool配合Event、WaitHandle同样能办到,但使用Task能以比较简洁的语法完成相同工做,使用.NET 4.0开发多线程时可多加利用。
到这里,咱们继续回到本来的.NET4.5中,首先咱们设计几种异步做业新旧写法法进行对比
private static void DownLoadWebPageSourceCode_Old() { WebClient wc = new WebClient(); wc.DownloadStringCompleted += CompletedHandler; wc.DownloadStringAsync(new Uri("http://www.cnblogs.com/rohelm")); while (wc.IsBusy) { Console.WriteLine("还没下完,我喝一回茶!"); } } private static void CompletedHandler(object sender, DownloadStringCompletedEventArgs e) { Console.WriteLine(e.Result); }
运行效果以下:
private static async void DownLoadWebPageSourceCode_New() { WebClient wc = new WebClient(); Console.WriteLine(await wc.DownloadStringTaskAsync("http://www.cnblogs.com/rohelm")); }
而它的内部实现机制其实是咱们前面的附加专题中提到的Task,咱们来查看下这个方法的源码:
[ComVisible(false), HostProtection(SecurityAction.LinkDemand, ExternalThreading=true)] public Task<string> DownloadStringTaskAsync(string address) { return this.DownloadStringTaskAsync(this.GetUri(address)); } [ComVisible(false), HostProtection(SecurityAction.LinkDemand, ExternalThreading=true)] public Task<string> DownloadStringTaskAsync(Uri address) { TaskCompletionSource<string> tcs = new TaskCompletionSource<string>(address); DownloadStringCompletedEventHandler handler = null; handler = delegate (object sender, DownloadStringCompletedEventArgs e) { this.HandleCompletion<DownloadStringCompletedEventArgs, DownloadStringCompletedEventHandler, string>(tcs, e, args => args.Result, handler, delegate (WebClient webClient, DownloadStringCompletedEventHandler completion) { webClient.DownloadStringCompleted -= completion; }); }; this.DownloadStringCompleted += handler; try { this.DownloadStringAsync(address, tcs); } catch { this.DownloadStringCompleted -= handler; throw; } return tcs.Task; }
因为上面咱们已经说过它的内部本质仍是Task因此它的,取消该非同步做业依旧借助CancellationTokenSource及其所属CancellationToken类型
private static async Task TryTask() { CancellationTokenSource cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(1)); Task<string> task = Task.Run(() => PirntWords("Hello,Wrold!", cts.Token), cts.Token); Console.WriteLine(task.Result); await task; } private static string PirntWords(string input, CancellationToken token) { for (int i = 0; i < 20000000; i++) { Console.WriteLine(input); token.ThrowIfCancellationRequested(); } return input; }
public async Task<ActionResult> DoAsync() { ServiceClient1 client1 = new ServiceClient1(); ServiceClient2 client2 = new ServiceClient2(); var task1 = client1.GetDataAsync(); var task2 = client2.GetDataAsync(); await Task.WhenAll(task1,task2); return View("View名称",new DataModel(task1.Result,task2.Rusult)); }
是否是发现很是的方便,实用啊!
未完待续....
后续内容:
对WebAPI和WCF的进行一个简单比较,探讨WebAPI的机制,功能,架构,WinFrom Client/WebService Client大数据上传...备注:本文章版权的没有,归.NET使用者共有。