以前写过有关异步的文章,对这方面一直比较弱,感受仍是不太理解,因而会花点时间去好好学习这一块,咱们由浅入深,文中如有叙述不稳妥之处,还请批评指正。html
(1)是否是将方法用async关键字标识就是异步方法了呢?编程
(2)是否是没有await关键字的存在async就没有存在的意义了呢?异步
(3)用异步方法的条件是什么呢,为何会有这个条件限制?async
(4)只能调用.NET Framework内置的用await标识的Task,可否自定义实现呢?异步编程
(5)在lambda表达式中是否能够用async和await关键字来实现异步呢(即异步lambda表达式)?学习
上述抛出这几个话题,明白本文主要讲述的话题以及须要深刻了解的知识。this
注意:这里我将参照园友【反骨仔】的文章进行进一步解析。spa
例如异步方法是这样的:线程
public static async Task<int> asyncMethod() { return await Task.Run(() => Calculate()); } static int Calculate() { return 1 + 1; }
那要是以下这样写呢?code
public static async Task<int> asyncMethod() { var task = Task.Run(() => Calculate()); return task.Result; }
那上述这种写法是否是也是异步方法呢?答案是【NO】,既然不是异步方法为何要用async关键字来进行标识呢?不是很容易被咱们所误解呢?好了疑问这么多咱们一一来解惑。
(1)告诉编译器这个方法里面可能会用到await关键字来标识该方法是异步的,如此以后,编译器将会在状态机中编译此方法。接着该方法执行到await关键字时会处于挂起的状态直到该异步动做完成后才恢复继续执行方法后面的动做。
(2)告诉编译器解析出方法的结果到返回类型中,好比说Task或者Task<TResult>,也就是说将返回值存储到Task中,若是返回值为void那么此时应该会将可能出现的异常存储到上下文中。
当将方法用async标识时且返回值为void或者Task或者Task<TReuslt>,此时该方法会在当前线程中一直同步执行。用async标识方法并不会影响方法运行完成是不是同步或者异步,相反,它可以将方法划分红多块,有可能有些在异步中运行,以致于这些方法是异步完成的,而划分异步和同步方法的边界就是使用await关键字。也就是说若是在方法中未用到await关键字时则该方法就是一整块没有所谓的划分,会在同步中运行,在同步中完成。
显然不是这样,当用async标识方法时只是显示告诉编译器在该方法中await关键字可能会被用到,当执行到await关键字开始处于挂起的状态知道异步动做执行完成才恢复(异步操做是在状态机中【有关状态机请看这里:Async和Await异步编程的原理】完成,完成后此时才会建立一个线程),这也就是为何在方法中方法用async标识若是没有用到await关键字IDE会发出警告的缘由。
—————————————————————————————————————————————————————————————————
到了这里咱们能够得出结论:不管方法是同步仍是异步均可以用async关键字来进行标识,由于用async标识只是显示代表在该方法内可能会用到await关键字使其变为异步方法,并且将该异步方法进行了明确的划分,只有用了await关键字时才是异步操做,其他一并为同步操做。
返回类型必须为void或者Task或者Task<TResult>和关键字的标识以及其余就再也不叙述,其中有一条是不能使用ref和out关键字,你是背书似的铭记了这一条,仍是略加思索了呢?你想过没有为什么不可呢?
咱们知道用ref和out关键字不过是为了在方法里面改变其值,也就是是当同步完成时咱们指望被ref或者out关键字修饰的值会被设置,可是它们可能在异步完成时或者以后才会被设置达不到咱们预期,因此在异步方法中不能用ref和out关键字。
返回类型Task参数能够为lambda表达式或者匿名方法对象,那直接对lambda表达式异步是否可行?下面咱们来看看
public static async Task<T2> CallFuncAsync<T1, T2>(T1 t, Func<T1, T2> func) { return func.Invoke(t); } public static async Task<string> GetStringAsync(int value) { return await Task.Run(() => "xpy0928"); } public static async Task MainAsync() { string value = await CallFuncAsync<int, string>(30, async (s) => await GetStringAsync(s)); }
编译后生成以下错误:
由上知异步lambda表达式是不行的,猜想是异步lambda表达式不能转换为表达式树,同时咱们看看上述代码,CallFunAsync此时并未是异步方法,上述咱们已经叙述过,此时是同步运行,既然上述错误,而且代码也有不可取之处咱们接下来一一进行修改。
string value = await CallFuncAsync<int, string>(30, async (s) => await GetStringAsync(s)); 修改成: string value = await CallFuncAsync<int, string>(30, s => GetStringAsync(s).Result);
解决了编译错误,可是未解决CallFuncAsync为异步运行,咱们将其修改成异步运行。既然await是针对于Task而操做,咱们将CallFuncAsync中的返回参数设置为Task便可。
public static async Task<T2> CallFuncAsync<T1, T2>(T1 t, Func<T1, Task<T2>> func) { return await func.Invoke(t); }
则最终调用时咱们直接调用便可。
string value = await CallFuncAsync(30, GetStringAsync);
此时CallFuncAsync才算是异步运行。
对于异步表达式有一点其实表述不太正确,其实我一直仍是有点怀疑异步lambda表达式真的不行吗,此刻我竟然发现这样是能够的:
var task = Task.Run(async () => { await Task.Delay(TimeSpan.FromMilliseconds(5000)); });
如上不正是异步表达式的影子吗,因而我将上述代码进行了改写,以下:
public static async Task<Action> CallFuncAsync(Action action) { return action; } public static async Task<Action> GetStringAsync() { return () => Console.WriteLine("xpy0928"); } public static async Task MainAsync() { await CallFuncAsync(async () => await GetStringAsync()); }
此时编译经过,说明表述异步表达式并不是必定不能够,只是对于无参数的lambda表达式才能够,而对于有参数的lambda表达式如fun则不能执行异步lambda表达式。
至此能够基本下结论:
异步lambda表达式只对于无参数的lambda表达式 才能够(固然这也就没有了什么意义),而对于有参数的lambda表达式则产生编译错误则不能执行异步(猜想是没法转换成对应的表达式树)。(不知是否严谨或者不妥,如有错误之处,还望对此理解的更透彻的园友给出批评性意见)。
为了验证这一点,咱们来看看无参数的func委托例子,以下:
static async Task<string> GetTaskAsync() { await Task.Delay(TimeSpan.FromMilliseconds(5000)); return "xpy0928"; }
var task = Task.Run(async () => await GetTaskAsync());
此时无参数的func委托则编译经过,应该是验证了上述观点(仍是有点怀疑我所下的结论)。
await关键字是这样用的
await Task.Run(() => "xpy0928");
此时背后究竟发生了什么呢?咱们上述也说过异步动做时在状态机中完成,当执行到这里时,编译器会自动生成代码来检测该动做是否已经完成,若是已经完成则继续同步执行await关键字后面的代码,经过判断其状态机状态若未完成则会挂起一个继续的委托为await关键字的对象直到完成为止,调用这个继续动做的委托从新进入未完成的这样一个方法。
好比说: await someObject; 编译器则会生成以下代码:
private class FooAsyncStateMachine : IAsyncStateMachine { // Member fields for preserving “locals” and other necessary state int $state; TaskAwaiter $awaiter; … public void MoveNext() { // Jump table to get back to the right statement upon resumption switch (this.$state) { … case 2: goto Label2; … } … // Expansion of “await someObject;” this.$awaiter = someObject.GetAwaiter(); if (!this.$awaiter.IsCompleted) { this.$state = 2; this.$awaiter.OnCompleted(MoveNext); return; Label2: } this.$awaiter.GetResult(); … } }
此时讲到这里就要涉及到await背后具体的实现,在Task或者Task<TResult>类里面有这样一个返回类型为 TaskAwaiter 的 GetAwaiter 属性,而TaskAwaiter中有以下属性:
public struct TaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion { public bool IsCompleted { get; } public void GetResult(); public void OnCompleted(Action continuation); public void UnsafeOnCompleted(Action continuation); }
经过IsComplete来判断是否已经完成。这个有什么做用呢?经过看到背后具体实现,咱们能够本身简单实现异步扩展方法,当咱们在Task中查看其方法会有这样的提示:
下面咱们就来实现这样的效果,给TimeSpan添加异步方法:
public static class Extend { public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan) { return Task.Delay(timeSpan).GetAwaiter(); } }
此时异步方法则是这样的:
本节咱们详细讲述了async和await关键字的使用和一些基本原理以及解释其缘由,但愿经过对本文的学习,对你们可以更好的去理解异步,我也在学习中,Over。