概念
异步函数(asynchronous funcation)是TPL之上的更高级别的抽象,真正简化了异步编程。抽象隐藏了主要的实现细节,使得程序员无须考虑许多重要的事情,从而使异步编程更容易。
更多内容
创建异步函数,首先用async关键字标注一个方法(不能在Main中使用async),然后异步函数必须返回Task或Task<T>类型(不推荐使用async void方法)。在async关键字标注的方法内部,至少使用一个await操作符,否则会有编译警告。
在执行完await调用的代码行后该方法立即返回并将工作者线程放回线程池;如果是同步执行,执行线程将会阻塞两秒后返回结果。这允许在两秒或等待时间内将该工作者线程重用做其他事。这样可提高应用程序的可伸缩性。
借助异步函数,我们拥有了线性的程序控制流,但它的执行依然是异步的。如果程序连续出现两个await操作符,他们是顺序执行,先完成第一个第二个才会开始。
using
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static System.Threading.Thread; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Dynamic; using ImpromptuInterface;
使用await操作符获取异步任务结果
定义了两个异步操作。第一个是标准TPL模式代码:启动一个任务,两秒后返回结果,定义一个后续操作来打印结果,再定义一个后续操作来捕获异常;第二个使用async和await:直接获取任务结果并打印,通过try……catch来捕获异常。
class Program { static void Main(string[] args) { Task t = AsynchronyWithTPL(); t.Wait(); t = AsynchronyWithAwait(); t.Wait(); Console.ReadLine(); } static Task AsynchronyWithTPL() { Task<string> t = GetInfoAsync("Task1"); //Console.WriteLine("123" + t.Result); 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.WhenAny(t2, t3); } static async Task AsynchronyWithAwait() { try { string result = await GetInfoAsync("Task2"); Console.WriteLine(result); } catch (Exception ex) { Console.WriteLine(ex.Message); } } static async Task<string> GetInfoAsync(string name) { await Task.Delay(TimeSpan.FromSeconds(2)); //throw new Exception("1234"); return $"{name} is running in the thread id:{CurrentThread.ManagedThreadId} thread. Is thread pool thread:{CurrentThread.IsThreadPoolThread} "; } }
两种模式在概念上是等同的,但第二种模式隐式处理了异步代码。
在lambda表达式中使用await关键字
使用lambda定义一个匿名方法,形参类型string,返回值类型Task<string>类型(这里返回的string类型,编译器自动产生一个Task并返回)。
class Program { static void Main(string[] args) { Task t = AsynchronousProcessing(); t.Wait(); Console.ReadLine(); } static async Task AsynchronousProcessing() { Func<string, Task<string>> asyncLambda = async name =>//Func第一个参数是形参类型,第二个参数是返回类型 { await Task.Delay(TimeSpan.FromSeconds(2)); return $"{name} is running on a thread id {CurrentThread.ManagedThreadId}. Is thread pool thread:{CurrentThread.IsThreadPoolThread}";//返回的是string类型,编译器自动产生一个任务来返回一个Task<string> }; string result = await asyncLambda("Async lambda"); Console.WriteLine(result); } }
对连续的异步任务使用await操作符
两种模式的顺序执行:TPL顺序执行,使用Task.ContinueWith方法来指定下一步要执行的任务,并用该方法来打印结果与捕获异常;使用await与async,同样方法先执行到await代码行就会立即返回,剩下的代码将会在一个后续操作任务中运行。我们可以在Task.Wait方法之前,执行其他任务。
class Program { static void Main(string[] args) { Task t = AsynchronyWithTPL(); t.Wait(); t = AsynchronyWithAwait(); Console.WriteLine("123");//这里会在任务被执行前先执行 t.Wait(); Console.ReadLine(); } static async Task AsynchronyWithAwait() { try { //两个await任务会顺序执行,而不是并行 string result = await GetInfoAsync("Await task 1"); Console.WriteLine(result); result = await GetInfoAsync("Await task 2"); Console.WriteLine(result); } catch (Exception ex) { Console.WriteLine(ex.Message); ; } } static Task AsynchronyWithTPL() { var containerTask = new Task(() => { Task<string> t = GetInfoAsync("TPL1"); t.ContinueWith(task => { Console.WriteLine(t.Result); Task<string> t2 = GetInfoAsync("TPL2"); t2.ContinueWith(innerTask => Console.WriteLine(innerTask.Result), TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent);//后续任务1:输出结果 t2.ContinueWith(innerTask => Console.WriteLine(innerTask.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); }, TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent);//后续任务2:输出异常 t.ContinueWith(task => Console.WriteLine(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); }); containerTask.Start(); return containerTask; } static async Task<string> GetInfoAsync(string name) { Console.WriteLine($"Task {name} started……"); await Task.Delay(TimeSpan.FromSeconds(2)); if (name.Contains("2")) { await Task.Delay(TimeSpan.FromSeconds(2)); throw new Exception("Boom!"); } return $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}. Is thread pool thread:{CurrentThread.IsThreadPoolThread}"; } }
异步并不是总意味着并行执行。
对并行执行的异步任务使用await操作符
先创建两个任务,然后用await Task.WhenAll方法来接收任务结果集。注意两种延时的区别。
class Program { static void Main(string[] args) { Task t = AsynchronousProcessing(); t.Wait(); Console.ReadLine(); } static async Task AsynchronousProcessing() { Task<string> t1 = GetInfoAsync("Task1", 5); Task<string> t2 = GetInfoAsync("Task2", 3); string[] results = await Task.WhenAll(t1, t2); foreach (var v in results) { Console.WriteLine(v); } } static async Task<string> GetInfoAsync(string name, int seconds) { //await Task.Delay(TimeSpan.FromSeconds(seconds));//工作者线程执行到delay后,将下面代码块指定给线程后即返回线程池供其他任务使用 await Task.Run(() => Thread.Sleep(TimeSpan.FromSeconds(seconds)));//工作者线程执行sleep后,将堵塞线程指定的时间,该期间其他任务不可使用 Console.WriteLine($"{name} has done."); return $"Task {name} is running on a thread id:{CurrentThread.ManagedThreadId}. Is thread pool thread:{CurrentThread.IsThreadPoolThread}."; } }
处理异步操作中的异常
捕获使用await情景中的几种异常:1,只有一个await任务;2,有多个await任务但只能捕获第一个任务的具体异常;3,有多个await任务异常,能捕获所有任务异常;4,演示C#6.0新特性:可以在catch…finally中使用await操作符。
class Program { static void Main(string[] args) { Task t = AsynchronousProcessing(); t.Wait(); Console.ReadLine(); } static async Task AsynchronousProcessing() { Console.WriteLine("1.Single Exception"); try { string result = await GetInfoAsync("1.Single Exception", 2); Console.WriteLine(result); } catch (Exception ex) { Console.WriteLine($"Exception details:{ex}"); } Console.WriteLine(); Console.WriteLine("2.Multiple Exceptions"); Task<string> t1 = GetInfoAsync("2.Multiple Exceptions 1", 2); Task<string> t2 = GetInfoAsync("2.Multiple Exceptions 2", 3); try { string[] result = await Task.WhenAll(t1, t2); Console.WriteLine(result.Length); } catch (Exception ex) { Console.WriteLine($"Exception details:{ex}"); } Console.WriteLine(); Console.WriteLine("3.Mulitple exceptions with AggregateException"); t1 = GetInfoAsync("3.Mulitple exceptions with AggregateException 1", 3); t2 = GetInfoAsync("3.Mulitple exceptions with AggregateException 2", 2); Task<string[]> t3 = Task.WhenAll(t1, t2); try { string[] results = await t3; Console.WriteLine(results.Length); } catch { var ae = t3.Exception.Flatten(); var exceptions = ae.InnerExceptions; Console.WriteLine($"Exceptions caught:{exceptions.Count}"); foreach (var item in exceptions) { Console.WriteLine($"Exception details:{item}"); Console.WriteLine(); } } Console.WriteLine(); Console.WriteLine("4.Await in catch and finally blocks"); try { string result = await GetInfoAsync("4.Await in catch and finally blocks", 2); Console.WriteLine(result); } catch (Exception ex) { await Task.Delay(TimeSpan.FromSeconds(2)); Console.WriteLine($"Catch block with await: Exception details:{ex}"); } finally { await Task.Delay(TimeSpan.FromSeconds(1)); Console.WriteLine("Finally block"); } } static async Task<string> GetInfoAsync(string name, int seconds) { await Task.Delay(TimeSpan.FromSeconds(seconds));//工作者线程执行到delay后,将下面代码块指定给线程后即返回线程池供其他任务使用 throw new Exception("Boom from " + name); } }
避免使用捕获的同步上下文
一种使用常规的await操作符,一种使用带参数的ConfigureAwait方法,false参数明确指出不能对其使用捕获的同步上下文来运行后续操作代码。后一种方法花费时间短很多。
class Program { private static Label _label; [STAThread] static void Main(string[] args) { var app = new Application(); var win = new Window(); var panel = new StackPanel(); var button = new Button(); _label = new Label(); _label.FontSize = 32; _label.Height = 200; button.Height = 100; button.FontSize = 32; button.Content = new TextBlock { Text = "Start asynchronous operations" }; button.Click += Click; panel.Children.Add(_label); panel.Children.Add(button); win.Content = panel; app.Run(win); Console.ReadLine(); } static async void Click(object sender, EventArgs e) { _label.Content = new TextBlock { Text = "Calculating……" }; TimeSpan resultWithContext = await Test(); TimeSpan resultNoContext = await TestNoContext(); //TimeSpan resultNoContext = await TestNoContext().ConfigureAwait(false); var sb = new StringBuilder(); sb.AppendLine($"With the context:{resultWithContext}"); sb.AppendLine($"Without the context:{resultNoContext}"); sb.AppendLine($"Ratio:{resultWithContext.TotalMilliseconds / resultNoContext.TotalMilliseconds:0.00}"); _label.Content = new TextBlock { Text = sb.ToString() }; } static async Task<TimeSpan> Test() { const int interationNumber = 100000; var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < interationNumber; i++) { var t = Task.Run(() => { }); await t; } sw.Stop(); return sw.Elapsed; } static async Task<TimeSpan> TestNoContext() { const int interationNumber = 100000; var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < interationNumber; i++) { var t = Task.Run(() => { }); await t.ConfigureAwait(continueOnCapturedContext: false);//尝试将延续任务封送回原始上下文,则为 true;否则为 false。 } sw.Stop(); return sw.Elapsed; } }
注:本文是在阅读《C#多线程编程实战》后所写,部分内容引用该书内容,这是一本不错的书,感谢!