该文为对个人认知,可能将存有严重错误。
如前所述各项任务的触发器商业模式(TAP)
Task-based Asynchronous Pattern(TAP)是.NET4.0面世的一类捷伊触发器程式设计商业模式,TAP的民族特色是采用原则上的方式来调用和同时实现触发器,使触发器程式设计的标识符很简约。在System.Threading.Tasks 重新命名内部空间里,提供更多了名叫 Task 的类,每两个 Task 代表者两个触发器操作方式。比如,上面的标识符展现了四种方式建立并触发器继续执行Task:
两个可能将的输出如下,但是上面四行输出任何顺序的情况都是可能将出现的。
注意到这里已经同时实现了触发器,且每个Task继续执行的线程是不同的,这些线程其实是.NET的ThreadPool(两个快速的线程池,在.NET用于继续执行Task和处理IO等各项任务)分配的空闲线程。
你也可以通过 RunSynchronously 方式同步继续执行两个Task。
上面的触发器操作方式都没有返回值,可以建立具有返回值的Task<T>,并通过 Re
Start方式是立即返回的,不会导致阻塞,这时程序已经有两个同时继续执行的分支了,其中两个继续执行Task指定的触发器方式,而另两个分支(主线程)继续继续执行Start方式上面的标识符,直到遇到 task.Result,此时:
ask还未继续执行完毕,则程序阻塞,并等待结果返回实际上每两个Task代表者两个触发器方式时,也承诺了在之后通过 Result 给出该方式的返回值(Task也包含其他信息,比如各项任务是否完成,以及是否出现异常),而且上面的标识符,虽然返回值类型是 Task<string>,但是在返回时直接返回了 string,这个包装的过程实际上是由编译器自动完成的。
上面介绍Task常用的操作方式:
Wait:等待当前 Task 实例继续执行完毕Task.WaitAny:静态方式,等待参数中某两个Task继续执行完毕Task.WaitAll:静态方式,等待参数中所有Task继续执行完毕这三个方式都会造成阻塞,比如:
如果不想造成阻塞,并且在Task没结束时就指定后续标识符,可以采用 Task.WhenAll/Task.WhenAny/ContinueWith 方式:
Task.WhenAll:静态方式,返回两个Task,它等待参数指定的所有 Task 结束才结束Task.WhenAny:静态方式,返回两个Task,它等待参数指定的某两个 Task 结束才结束ContinueWith:为当前 Task 实例指定两个结束后继续执行的Task采用 async/await
async 和 await 关键字提供更多了一类更为简约的方式同时实现触发器,async 用于修饰两个方式,标识它可以被触发器继续执行,一般情况下,两个触发器方式的返回值类型应该为 Task 或者 Task<T>,在UI事件中可以为 void,而 await 则用于等待某个异
首先,任何采用了 await 关键字的方式都是触发器方式(由 async 修饰的方式),需要采用 async 进行修饰,反过来,任何采用了 async 修饰的方式,都一定存有至少两个 await,任何触发器方式的名称都推荐以 Async 结尾,async和await的搭配形成了嵌套,然后一层一层地继续执行下去。具体来说,上面的 Test 方式继续执行顺序如下:
程序建立两个 HttpClient gAsync 方式时,程序已经通知系统发送这个网络请求,系统机制会再通知硬件处理这个网络请求在网络请求发出之后,程序继续往下继续执行,调用了 SomeWork 方式,输出 “Hello World”,注意,输出长度,触发器方式结束。若网络请求还未完成,那么程序将继续执行权移交到当前触发器方式的调用方,也是 Main 方式,Main 方式里其实也是一样的道理,输出 “Main”,并等待 ptask(若 Main 方式还被另一方式调用了,那么继续执行权会继续转交给调用方),而ptask在等待task,当task结束后,程序会回到 TestAsync 原来的标识符处继续继续执行。故上面的标识符输出为:
另外两个需要注意的是,在遇到 await 之前,这个方式都是同步继续执行的,即 “Hello World!” 总是先于 “Main” 输出。
可以看到,采用async和await同时实现触发器,关键是上面几点:
调用触发器方式后不会阻塞,而是立即返回两个 Task,这个 Task 承诺在之后给出触发器方式的返回情况和返回值在调用触发器方式之后,采用await之交给当前触发器方式的调用方,从而让程序等待的同时可以继续执行其他标识符如果你不需要在await之前继续执行独立的工作,可以直接采用上面这种更简约的写法:
上面讲到的关于 Task 的常用方式,搭配 await 采用更加推荐:
在程序进行I/O处理,或者UI事件响应时,采用async/await是十分高效率的。
async/await ≠ 多线程
在介绍TAP时,我们可以看到Task继续执行的时候是采用了ThreadPool的空闲线程的,而async/await则有一些不同。我们需要先了解一下触发器操作方式的两种类别:
I/O关联的触发器操作方式(IO-bound Operation):在硬件(如硬盘、网卡)进行,不需要线程,也不占用CPU,如读取网络资源或文件CPU关联的触发器操作方式(CPU-bound Operation):需要大量采用CPU,需要其他线程,如长时间的计算以UI程序为例,采用触发器可以防止界面假死,而下图展现了上面两种情况继续执行时的流程:
如果 I/O 关联的触发器操作方式不采用其他线程,那么它是如何做到触发器呢?简单地说,由于读取网络资源或者读取文件的操作方式可以由特定的硬件完成,而不依赖CPU,所以 .NET 通过向操作方式系统发送对应的请求(通过较低层次的.NET API 实现),由系统通知硬件进行处理,在请求的同时,.NET 向操作方式系统写入了两个回调函数(委托),在硬件处理完各项任务并通知操作方式系统时,操作方式系统会继续执行这个回调函数,这个函数会改变 Task的状态, 同时 .NE章:There Is No Thread
内部的两个静态类,这个类维护着一定数量的线程,在 .NET 需要时被采用,比如进行触发器操作方式,一旦线程继续执行完分配的各项任务,会立即进入休眠状态,这样的设计相比重复的建立新线程来说,更加高效。
在实际采用 await/async 中,是否需要采用另两个线程,还由其他因素决定,比如当前环境下的 SynchronizationContext 类,它规定了上下文切换时的一些配置。但总的来说,如前所述 await/async 的触发器程式设计是不会主动去建立新线程的,这与多线程程式设计十分不同,前者性能也更好。相比直接建立Task并通过Run等方式(这些方式总是会采用其他线程)继续执行而言,await/async 也更进一步,标识符也更加简约。
可以看出,在需要大量继续执行 I/O 操作方式时,await/async 有着巨大的优势,特别是网络服务器接受大量需要进行IO的请求时,await/async 能在IO时释放线程,让服务器有限的线程得以接受更多的网络请求,而不是造成阻塞,这有利于增加服务器的吞吐量。