简介:
前面异步编程的不足:它允 许我们以模块化的方式设计程序,来组合不同的异步操作。遗憾的是:
一: 当阅读此类程序时仍然非常难理解程序的实际执行顺序。在大型程序中将会有许多相互依赖的任务和后续操作,用于运行其他后续操作的后续操作,处理异常的后续操 作,并且它们都出现在程序代码中不同的地方。因此了解程序的先后执行次序变成了一个极具挑战性的问题。
二: 另一个需要关注的问题是,能够接触用户界面控制器的每个异步任务是否得到了正确的同步上下文。程序只允许通过UI线程使用这些控制器,否则将会得到多线程访问异常。说到异常,我们不得不使用单独的后续操作任务来处理在之前的异步操作中发生的错 误。这又导致了分散在代码的不同部分的复杂的处理错误的代码,逻辑上无法相互关联:
await的作用:为了解决这些问题,C# 5.0引入了新的语言特性,称为异步函数(asynchronous function)它是TPL之上的更高级别的抽象,真正简化了异步编程。正如在第4章提到的, 抽象隐藏了主要的实现细节,使得程序员无须考虑许多重要的事情,从而使异步编程更容 易。
三: async:要创建一个异步函数,首先需要用async关键字标注一个方法。如果不先做这个,就不可能拥有async属性或事件访问方法和构造函数。异步函数必须返回Task或Task<T>类型 可以使用async void 方法,但是更推荐使用async Task方法。使用async void方法唯一合理的地方是在程序中使用顶层UI控制器事件处理器的时候
async Task<string> GetStringAsync() ( await Task.Delay(TimeSpan.FromSeconds(2)); return *'Hello/ World!n ; }
四:使用async关键字标注的方法内部,可以使用await操作符。该操作符可与TPL的任务 一起工作,并获取该任务中异步操作的结果。在本章中稍后会讲述细节。在async方法外不 能使用await关键字,否则会有编译错误。另外,异步函数在其代码中至少要拥有一个await 操作符。然而,如果没有只会导致编译警告,而不是编译错误。
五:需要注意的是,在执行完await调用的代码行后该方法会立即返回。如果是同步执 行,执行线程将会阻塞两秒然后返回结果-这里当执行完await操作后,立即将工作者线程放回线程池的过程中,我们会异步等待。2秒后,我们又一次从线程池中得到工作者线 程并继续运行其中剩余的异步方法. 这允许我们在等待2秒时重用工作者线程做些其他 事,这对提高应用程序的可伸缩性非常重要:借助于异步函数我们拥有了线性的程序控制 流,但它的执行依然是异步的。这虽然好用,但是难以理解.如果程序中有两个连续的await操作符,此时程序如何工作有 —个常见的误解。很多人认为如果在另一个异步操作之后使用await函数,它们将会 并行运行。然而,事实上它们是顺序运行的,即第一个完成后第二个才会开始运行
六:注意事项:
a. 关联async和await有一定的限制°例如,在C# 5.0中,不能把控制台程序的Main方法 标记为async。不能在catch、finally. lock或unsafe代码块中使用await操作符。不允许对任 何异步函数使用ref或out参数。还有其他微妙的地方,但是以上已经包括了主要的需要注意 的地方。C#6.0去除了其中一些限制,由于编译器内部进行了改进,可以在catch和finally 代码块中使用await关键字.
b. 尽管 很多程序员几乎开始为每个方法使用async修饰符,我还是想强调如果方法本来无需异步 或并行运行,那么将该方法标注为async是没有道理的。调用async方法会有显著的性能 损失,通常的方法调用比使用async关键字的同样的方法调用要快上40〜50倍°请注意 这一点
1. 使用await操作符获取异步任务结果
class Program { static void Main(string[] args) { Task t = AsynchronyWithTPL(); t.Wait(); t = AsynchronyWithAwait(); t.Wait(); Read(); } //当程序运行时运行了两个异步操作。其中一个是标准的TPL模式的代码,第二个使用了 C#的新特性async和awaiL AsynchronyWithTPL方法启动了一个任务, //运行两秒后返回关于工作者线程信息的字符串。然后我们定义了一个后续操作,用于在异步操作完成后打印出 该操作结果, //还有另一个后续操作,用于万一有错误发生时打印出异常的细节。最终,返回 了一个代表其中一个后续操作任务的任务,并等待其在Main函数中完成 static Task AsynchronyWithTPL() { Task<string> t = GetInfoAsync("Task 1"); Task t2 = t.ContinueWith(task => WriteLine(t.Result), TaskContinuationOptions.NotOnFaulted); Task t3 = t.ContinueWith(task => WriteLine(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted); return Task.WhenAny(t2, t3); } //在AsynchronyWithAwait方法中,我们对任务使用await并得到了相同的结果。这和编 写通常的同步代码的风格一样,即我们获取任务的结果,打印出结果, //如果任务完成时带有 错误则捕获异常。关键不同的是这实际上是一个异步程序。使用await后,C#立即创建了一 个任务,其有一个后续操作任务, //包含了 await操作符后面的所有剩余代码。这个新任务也 处理了异常传播。然后,将该任务返回到主方法中并等待其完成 static async Task AsynchronyWithAwait() { try { string result = await GetInfoAsync("Task 2"); WriteLine(result); } catch (Exception ex) { WriteLine(ex); } } static async Task<string> GetInfoAsync(string name) { 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获得结果
2. 在lambda表达式中使用await操作符
class Program { static void Main(string[] args) { Task t = AsynchronousProcessing(); t.Wait(); } //首先,由于不能在Main方法中使用async,我们将异步函数移到了 Asynchronous- Processing 方法中° //然后使用async关键字声明了一个lambda表达式:由于任何lambda 表达式的类型都不能通过lambda自身来推断, //所以不得不显式向C#编译器指定它 的类型。在本例中,该类型说明该lambda表达式接受一个字符串参数,并返回一个 Task<string> 对象 //我们定义了 lambda表达式体。有个问题是该方法被定义为返回一个Task<string> 对象,但实际上返回的是字符串, //却没有编译错误!这是因为C#编译器自动产生一个任务 并返回给我们。最后一步是等待异步lambda表达式执行并打印岀结果. static async Task AsynchronousProcessing() { Func<string, Task<string>> asyncLambda = async name => { await Task.Delay(TimeSpan.FromSeconds(2)); return $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}." + $" Is thread pool thread: {CurrentThread.IsThreadPoolThread}"; }; string result = await asyncLambda("async lambda"); WriteLine(result); } }lambda表达式中使用await操作符
3. 对连续的异步任务使用await操作符(当代码中有多个连续的await方法时程序的实际流程是怎样的)
class Program { static void Main(string[] args) { Task t = AsynchronyWithTPL(); t.Wait(); t = AsynchronyWithAwait(); t.Wait(); } /*AsynchronyWithTPL方法模仿了 AsynchronyWithAwait的程序流。我们需要一个容器任 务来处理所有相互依赖的任务。然后启动主任务,给其加了一组后续操作。 * 当该任务完成 后,会打印出其结果。然后又启动了一个任务,在该任务完成后会依次运行更多的后续操 作。为了测试对异常的处理, * 当运行第二个任务时故意抛出一个异常,并打印出异常信息。 这组后续操作创建了与第一个方法中一样的程序流。如果用它与await方法比较, * 可以看到 它更容易阅读和理解。唯一的技巧是请记住异步并不总是意味着并行执行 */ static Task AsynchronyWithTPL() { var containerTask = new Task(() => { Task<string> t = GetInfoAsync("TPL 1"); t.ContinueWith(task => { WriteLine(t.Result); Task<string> t2 = GetInfoAsync("TPL 2"); t2.ContinueWith(innerTask => WriteLine(innerTask.Result), TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent); t2.ContinueWith(innerTask => WriteLine(innerTask.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); }, TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent); t.ContinueWith(task => WriteLine(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); }); containerTask.Start(); return containerTask; } /*使用了两个await声明。最 重要的一点是该代码依然是顺序执行的,Async 2任务只有等之前的任务完成后才会开始执 行。 * 当阅读该代码时,程序流很清晰,可以看到什么先运行,什么后运行。但该程序如何 是异步程序呢?首先,它不总是异步的。 * 当使用await时如果一个任务已经完成,我们会异步地得到该任务结果。否则,当在代码中看到await声明时, * 通常的行为是方法执行到该await代码行时将立即返回,并且剩下的代码将会在一个后续操作任务中运行。 * 因此等 待操作结果时并没有阻塞程序执行,这是一个异步调用。当AsynchronyWithAwait方法中 的代码在执行时, * 除了在Main方法中调用t.Wait外,我们可以执行任何其他任务。然而, 主线程必须等待直到所有异步操作完成, * 否则主线程完成后所有运行异步操作的后台线程 会停止运行 */ static async Task AsynchronyWithAwait() { try { string result = await GetInfoAsync("Async 1"); WriteLine(result); result = await GetInfoAsync("Async 2"); WriteLine(result); } catch (Exception ex) { WriteLine(ex); } } static async Task<string> GetInfoAsync(string name) { WriteLine($"Task {name} started!"); await Task.Delay(TimeSpan.FromSeconds(2)); if(name == "TPL 2") throw new Exception("Boom!"); return $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}." + $" Is thread pool thread: {CurrentThread.IsThreadPoolThread}"; } }连续await
标签:异步,Task,C#,await,---,任务,WriteLine,async From: https://www.cnblogs.com/apple-hu/p/18470474