1.甚么是促发器方式
句法上有如下表所示特点:
方式采用async做为缩排方式外部包涵两个或是数个await表达式方式回到类别要是 void 、Task 、Task<T>二者中众所周知促发器方式的模块能任一类别,但无法为out和ref模块同义词,通常促发器方式都是以 Async做为前缀的。除方式以外,Lambda表达式和非官方表达式也能做为促发器第一类。static void Main(string[] args) { Task<int> t = DoSumAsync(1, 2); Console.WriteLine(“结论:{0}”, t.Result); Console.ReadKey(); }//2.促发器方式 public static async Task<int> DoSumAsync(int a, int b) { //3.await 表达式 int sum = awaitTask.Run(() => {return a + b; }); return sum; }回到类别简述:
Task类别:假如初始化方式不须要从促发器方式中回到某一值,但须要检查和促发器方式的状况,能回到两个Task,这时就算促发器方式中再次出现了return句子,也不能回到任何人小东西(只不过都不须要表明采用return句子)。Task<T>类别,除下面Task的机能,还能透过 Return特性来回到T类别的值。void类别:假如实际上是继续执行促发器方式,而不须要与它做任何人更进一步的可视化(“初始化并忘掉”),这时能用void,和Task那样,即使有return句子,也不能获得任何人小东西(只不过都不须要表明采用return句子)。特别注意,返回值为void的促发器表达式后面无法采用await。而初始化其它三种回到值的促发器方式时,假如没用await,C++会得出警示。回到类别采用准则:
对须要回到第一类的方式,采用Task<T>当两个方式归属于促发后不必理睬甚么时候完成的方式,能采用void,例如事件处理表达式(Event Handler)当虽然不须要回到结论,但却须要知道是否继续执行完成的方式时,回到两个Task。例如促发器方式涉及数据更新逻辑,而初始化该促发器方式的方式在须要在初始化促发器方式后读取最新数据,这种情况须要用Task回到类别,而无法用void,否则可能读取不到最新数据。促发器方式的创建和采用三个部分组成:
初始化方式(calling method):该方式初始化促发器方式,在促发器方式继续执行其任务的时候继续继续执行促发器方式(async)await表达式:用于促发器方式外部,指明须要促发器继续执行的内容。两个促发器方式能包涵任一数个await表达式,假如两个都不包涵C++会发出警示促发器方式由三部分组成:
await之前的部分await表达式后续部分:await之后的部分(假如有数个await,那就是await之间和最后两个await之后的部分)这里有几个须要特别注意的问题:
await之前的部分是同步继续执行的(确切说是第两个await之前)当达到awati的时候,会将促发器方式的控制回到给初始化方式。假如方式回到的类别是Task或是Task<T>,将创建两个Task第一类(表示需促发器完成的任务和后续)回到到初始化方式。 这里的回到值并不是await表达式的回到值,而是促发器方式中声明的回到值类别(即Task或是Task<T>)促发器方式外部须要完成以下工作:促发器继续执行await表达是的空闲任务当await表达式继续执行完成之后,继续执行后续部分。后续本身也可能是await表达式,处理过程和上两个一致。后续部分假如遇到 return或是方式达到末尾,将做如下表所示的事情:(这个点要注2.促发器方式在非UI线程继续执行
static void Main(string[] args) {MethodAsync1(); Console.Read(); } static async void MethodAsync1() { Console.WriteLine(“当前主线程ID为:” + Thread.CurrentThread.ManagedThreadId ); Console.WriteLine(“是否为线程池线程:”+ Thread.CurrentThread.IsThreadPoolThread);await Task.Run(() => { Console.WriteLine(“第两个await当前线程ID为:”+ Thread.CurrentThread.ManagedThreadId );Console.WriteLine(“是否为线程池线程:”+ Thread.CurrentThread.IsThreadPoolThread); });Console.WriteLine(“第两个await结束后当前线程ID为:” + Thread.CurrentThread.ManagedThreadId); Console.WriteLine(“是否为线程池线程:” + Thread.CurrentThread.IsThreadPoolThread); await Task.Run(() => { Console.WriteLine(“第二个await当前线程ID为:” + Thread.CurrentThread.ManagedThreadId ); Console.WriteLine(“是否为线程池线程:”+ Thread.CurrentThread.IsThreadPoolThread); });Console.WriteLine(“第二个await结束后当前线程ID为:”+ Thread.CurrentThread.ManagedThreadId );Console.WriteLine(“是否为线程池线程:” + Thread.CurrentThread.IsThreadPoolThread); }该演示是在控制台应用程序中完成的,我们能看到,主线程的ID为10,第两个await和紧接着之后的代码的线程ID为6和11,第二个await和紧接着之后的代码的线程ID为6,在非UI的线程中继续执行async促发器方式,aw
3.促发器方式在UI线程继续执行
private voidbutton1_Click(object sender, EventArgs e) { MethodAsync1(); }string message; async voidMethodAsync1() { message += (“当前主线程ID为:” + Thread.CurrentThread.ManagedThreadId + “\r\n”); message += (“是否为线程池线程:” + Thread.CurrentThread.IsThreadPoolThread + “\r\n”); await Task.Run(() => { message += (“第两个await当前线程ID为:” + Thread.CurrentThread.ManagedThreadId + “\r\n”); message += (“是否为线程池线程:”+ Thread.CurrentThread.IsThreadPoolThread +“\r\n”); }); message += (“第两个await结束后当前线程ID为:”+ Thread.CurrentThread.ManagedThreadId +“\r\n”); message += (“是否为线程池线程:”+ Thread.CurrentThread.IsThreadPoolThread +“\r\n”); await Task.Run(() => { message += (“第二个await当前线程ID为:”+ Thread.CurrentThread.ManagedThreadId +“\r\n”); message += (“是否为线程池线程:” + Thread.CurrentThread.IsThreadPoolThread + “\r\n”); }); message += (“第二个await结束后当前线程ID为:” + Thread.CurrentThread.ManagedThreadId + “\r\n”); message += (“是否为线程池线程:” + Thread.CurrentThread.IsThreadPoolThread + “\r\n”); this.richTextBox1.Text = message; }这个演示能看到,在UI线程中采用async促发器方式的时候,await后紧接着的代码,一直都会是在UI线程中继续执行。因此,在采用的时候须要特别注意这一点,在UI与非UI线程中继续执行async促发器方式的时候,须要特别注意这两个细节,否则将会带来不必要的麻烦,例如:
static void Main(string[] args) { vara = MethodAsync1(); Console.WriteLine(a.Result); Console.Read(); }static async Task<int> MethodAsync1() { return awaitTask.Run(() => { Thread.Sleep(1000); return 1; }); }如代码所示,定义两个Task类别回到值的促发器方式,先继续执行这个促发器方式MethodAsync1(但在UI线程时,如下表所示所示:
private void button1_Click(object sender, EventArgs e) { vara = MethodAsync1(); richTextBox1.Text = a.Result.ToString(); }async Task<int> MethodAsync1() { await Task.Run(() => { Thread.Sleep(1000); });return 1; }orm程序来继续执行,该促发器方式是在UI线程初始化的,那么,现在就再次出现问题了,一旦点击button1按钮,程序便卡死了。
下面说过了,在UI线程中初始化的促发器方式,在其await操作之后的一些代码,仍然实在UI线程中继续执行的,那么,首先,点击button1按钮按钮,促发器方式继续执行await中的促发器任务,初始化表达式继续继续执行到 richTextBox1.Text = a.Result.ToString()这句被阻塞UI线程等待促发器结论,而await任务继续执行完以后,return 1这个操作也须要在UI线程中继续执行,但UI已经被阻塞了,无法继续执行任何人代码,就这样,我在等你回到值,你在等我释放UI线程来给你继续执行代码,谁也不相让,造成了程序的卡死。
只不过下面的写法有问题,下面正确的写法能避免卡死:
private void button1_Click(object sender, EventArgs e) { vart = Task.Run(() => { Thread.Sleep(1000); return 1; }); richTextBox1.Text = await t; }4.异常处理
Async Task或 Aync Task<T> 意味着该方式实际上会自动回到对正在进行的操作的引用,然后您能在其它地方等待它。在这个回到的引用中,包括异常第一类,以及我们等的结论和状况。而async void,没Task第一类,并无法回到这些内容。所以两着的异常处理有区别。
1.Async Task或 Aync Task<T>的异常流以上代码的运行结论如下表所示:
异常从 async Task ThrowException() 再次抛出后,在初始化表达式AwaitException_Event()处再被截获,这是我们期望的结论。
2.task.wait()的异常流将代码改为如下表所示,初始化ThrowException这个Task时,不采用await,而采用wait(),如下表所示
那么便得到这样的结论:
这是因为异常在Task外部都保存在AggregateException第一类里。每两个Task都会存两个异常列表。当你await 两个task时,第两个异常会重新抛出来,所以你能捕获指定的异常类别(比如InvalidOperationException)。但,当你采用Task.Wait或是Task.Result同步地阻塞Task时,所有的异常都被封装在AggregateException抛出来。可见 await task时,异常处理睬更容易。
假如在20行不必await,编译程序,有以下两个提醒:
warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the await operator to the result of the call.
warning CS1998: This async method lacks await operators and will run synchronously. Consider using the await operator to await non-blocking API calls, or await Task.Run(…) to do CPU-bound work on a background thread.
运行时发现结论不一样了,发现收不到异常了。异常当然不能凭空消失,但假如不必await,这个异常就像被“吞噬”那样。这是因为,初始化程序没await,没得到这个Task,同样无法得到这个Task中的异常,异常无法回到初始化代码的上下文环境。
可见,初始化async Task一定要采用 await。
3.async void的异常流将ThrowException的回到类别改为async void。那么这时C++也会告诉无法在初始化ThrowException的地方采用await。
运行后,我们得到这个结论,异常没被“吞噬”,而是直接抛出来了。在AwaitException_Event中的try…catch并无法catch到。这是因为这个方式没Task,在AwaitException_Event中我们也并无法await,所以异常就在程序当前上下文直接抛出来。因为这是控制台程序,假如在GUI(如WPF和UWP)程序中,在全局的UnHandled_Exception中,我们能收到这个异常。但假如有数个这样的async void抛出类似这种异常,我们并无法区分以有效处理。可见采用async void是非常危险的行为, 代码中也避免采用async void (除促发器事件或委托)。
以上三种情况可得,在async,await中,最佳的异常处理就是促发器表达式采用async Task或是async Task<T> ,使得初始化者能透过await得到表达式的引用,获得期间的结论和异常信息。