带着问题去思考!大家好
简介
微软提供的最新的异步编程基础设施。它允许我们以模块化的方式设计程序,来组合不同的异步操作。
1:遗憾的是,当阅读此类程序时仍然非常难理解程序实际执行顺序。很多大型的程序中将会有许多相互依赖的任务和后续操作,处理异常的后续操作,并且它们都会出现在程序代码中的不同地方,因此了解程序的先后执行次序很难。
2:能够接触用户界面控制的每个异步任务是否得到了正确的同步上下文。程序只允许通过UI线程使用这些控制器,否则将会得到多线程的访问异常。说到异常,我们不得不使用单独的后续操作任务来处理在之前的异步操作中发生的错误。这又导致分散在代码的不同部分的复杂的处理错误的代码。逻辑无法相互关联。
C#5.0引入了新的语言特性。异步函数,它是TPL之上的更高级别的抽象。真正简化了异步编程。
须知:
- 创建一个异步函数,首先需要用async关键字标注一个方法。
- 异步函数必须返回Task或Task<T>类型,可以使用async void 方法,但是更推荐使用async task方法,
- 使用async标注的方法内部,可以使用await操作符,该操作符可与TPL的任务一起工作,并获取该任务中异步操作的结果。
- 异步函数在其代码中至少拥有一个await操作符。(没有则只是警告)
- 不能在catch,finally,lock或unsafe代码块中使用await操作符。
- 不允许对任何异步函数使用ref或out参数。
如果程序中有两个连续await操作符,他们是顺序运行的,第一个完成后第二个才会开始运行
使用await操作符获取异步任务结果
/// <summary> /// 使用await获取结果 /// </summary> /// <returns></returns> public static Task AsynchronyWithTPL() { Task<string> t = GetInfoAsync("Task 1"); Task t2 = t.ContinueWith(task => Console.WriteLine(t.Result), TaskContinuationOptions.NotOnFaulted); Task t3 = t.ContinueWith(task => Console.WriteLine(t.Exception.InnerException),TaskContinuationOptions.OnlyOnFaulted); return Task.WhenAll(t2, t3); } async static Task AsynchronyWithAwait() { try { string result = await GetInfoAsync("Task 2"); Console.WriteLine(result); } catch (Exception ex) { Console.WriteLine(ex); } } private async static Task<string> GetInfoAsync(string name) { await Task.Delay(TimeSpan.FromSeconds(2));//创建在指定时间间隔后完成的任务。 return string.Format("Task {0} is running on a thread id {1}.Is thread pool thread :{2}",name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); } static void Main(string[] args) { Task t = AsynchronyWithTPL(); t.Wait(); t = AsynchronyWithAwait(); t.Wait(); }
这里需要注意的是Task.Wait和Task.Result方法,如果不是百分百知道代码是干什么的,很容易造成死锁。
对连续的异步任务使用await操作符
/// <summary> /// 对连续的异步任务使用await /// </summary> /// <returns></returns> public static Task AsynchronyWithTPL() { var containerTask = new Task(() => { Task<string> t = GetInfoAsync("TPL 1"); t.ContinueWith(task => { Console.WriteLine(t.Result); Task<string> t2 = GetInfoAsync("TPL 2"); t2.ContinueWith(innerTask => Console.WriteLine(innerTask.Result), TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent); t2.ContinueWith(innerTask => Console.WriteLine(innerTask.Exception.InnerException), TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent); }, TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent); t.ContinueWith(task => Console.WriteLine(t.Exception.InnerException), TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent); }); containerTask.Start(); return containerTask; } async static Task AsynchronyWithAwait() { try { string result = await GetInfoAsync("Async 1"); Console.WriteLine(result); result = await GetInfoAsync("Async 2"); Console.WriteLine(result); } catch (Exception ex) { Console.WriteLine(ex); } } private async static Task<string> GetInfoAsync(string name) { Console.WriteLine("任务 {0} 开始!",name); await Task.Delay(TimeSpan.FromSeconds(2));//创建在指定时间间隔后完成的任务。 if (name == "TPL 2") throw new Exception("Boom"); return string.Format("Task {0} is running on a thread id {1}.Is thread pool thread :{2}", name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); } static void Main(string[] args) { Task t = AsynchronyWithTPL(); t.Wait(); t = AsynchronyWithAwait(); t.Wait(); }
同样运行两个异步操作,首先AsynchronyWithAwait方法将讲,这里使用了两个await声明,最重要一点是该代码是顺序执行,阅读代码很清晰,但是该程序如何是异步程序呢?首先,他不总是异步的,当使用await时如果一个任务已经完成,我们会异步地得到该任务结果。否则,当代码中看到await声明时,通常的行为是方法执行到该await代码行时将立即返回,并且剩下的的代码将会在一个后续操作任务中运行,因此等待操作结果时并没有阻塞程序执行。这是一个异步调用。
异步并不总是意味着并行执行。
对并行执行的异步任务使用await操作符
async static Task AsynchronousProccessing() { Task<string> t1 = GetInfoAsync("task1",3); Task<string> t2 = GetInfoAsync("task2", 5); string[] results = await Task.WhenAll(t1, t2); foreach (var result in results) { Console.WriteLine(result); } } private async static Task<string> GetInfoAsync(string name,int seconds) { await Task.Run(() => { TimeSpan.FromSeconds(seconds); }); return string.Format("Task {0} is running on a thread id {1},Is thread pool thread {2}",name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); } static void Main(string[] args) { Task t = AsynchronousProccessing(); t.Wait(); }
这里定义了两个异步任务,分别执行了3秒和5秒。然后使用了Task.WhenAll辅助方法创建另一个任务,该任务只有在所有底层任务完成后才运行,之后我们等待该组合任务的结果。这里两个任务似乎是被线程池中的同一个线程执行的。
两个任务被不同的工作线程执行,不同之处在于Task.Delay在幕后使用了一个计时器,从线程池中获取工作线程,它将等待Task.Delay方法返回结果。然后,Task.Delay方法启动计时器并指向一块代码,该代码会在计时器时间到了Task.Delay方法中指定的秒数后被调用。之后即将工作线程返回到线程池中。当计时器事件运行时,我们又从线程池中任意获取一个可用的工作线程(可能就是运行一个任务时使用的线程
当使用Task.Run方法时,从线程池中获取一个工作线程并将其阻塞几秒,具体秒数由Thread.Sleep方法提供。然后获取了第二个。 工作线程并且将其阻塞。在这种场景下,我们消费了两个工作线程,而它们绝对什么事没做,因为在它们等待时不能执行任何其他操作。
处理异步操作中的异常
async static Task AsynchronousProcessing() { Console.WriteLine("1.Single exception"); try { string restult = await GetInfoAsync("Task 1", 2); Console.WriteLine(restult); } catch (Exception ex) { Console.WriteLine("Exception details:{0}", ex); } Console.WriteLine(); Console.WriteLine("2.Multiple exception"); Task<string> t1 = GetInfoAsync("Task 1", 3); Task<string> t2 = GetInfoAsync("Task 2", 2); try { string[] result = await Task.WhenAll(t1, t2); Console.WriteLine(result.Length); } catch (Exception ex) { Console.WriteLine("Exception details:{0}", ex); } Console.WriteLine(); Console.WriteLine("2. Multiple execption with AggregateException"); t1 = GetInfoAsync("Task 1", 3); t2 = GetInfoAsync("Task 2", 2); Task<string[]> t3 = Task.WhenAll(t1,t2); try { string[] results = await t3; Console.WriteLine(results.Length); } catch (Exception ex) { var ae = t3.Exception.Flatten(); var exceptions = ae.InnerExceptions; Console.WriteLine("Exceptions caught:{0}", exceptions.Count); foreach (var e in exceptions) { Console.WriteLine("Exception details :{0}",e); Console.WriteLine(); } } } private async static Task<string> GetInfoAsync(string name, int seconds) { await Task.Delay(TimeSpan.FromSeconds(seconds)); throw new Exception(string.Format("Boom from {0} ", name)); } static void Main(string[] args) { Task t = AsynchronousProcessing(); t.Wait(); }
这里实现了三个场景来展示在C#中使用async和await时关于错误处理的最常见情况,一种情况最简单,与同步代码几乎一样,使用try/catch声明即可获取异常细节。一种很常见的错误是对一个以上的异步操作使用await时还使用以上方式。如果跟第一种处理的话,则只能从底层的AggregateException对象中得到第一个异常。为了收集所有异常信息,可以使用await任务的Exception属性,在第三种情况中,我们使用AggregateException的Flatten方法将层级异常放入一个列表,并且从中提取出所有的底层异常。