我们提供安全,免费的手游软件下载!
多线程与异步是两个完全不同的概念,常常有人混淆。
异步适用于"IO密集型"的场景,它可以避免因为线程等待IO形成的线程饥饿,从而造成程序吞吐量的降低。其本质是:让线程的cpu片不再浪费在等待上,期间可以去干其它的事情。要注意的是:Async不能加速程序的执行,它只能做到不阻塞线程。
多线程适用于"CPU密集型",主要是为了更多的利用多核CPU来同时执行逻辑。将一个大任务分而治之,提高完成速度,进而提高程序的并发能力。值得注意的是,如果过多使用线程同步,会降低多线程的使用效果。
在计算机科学中,一个线程指的是在程序中一段连续的逻辑控制流。在业务很复杂的时候,一个线程无法满足现有业务需求,多线程编程就应运而生。
ReadAsync底层调用win32 API ReadFile,ReadFile分配IRP数据结构(句柄,读取偏移量,用来填充的byte[]),然后传递给windows内核中,windows把IRP添加到硬盘驱动的IRP队列中,线程不再阻塞,立刻返回到线程池中(在此期IRP尚未处理完成),读取硬盘数据,返回硬盘数据并组装IRP数据,将IRP Enqueue IO Completion Port,ThreadPool轮询Dequeue该端口,提取IRP,执行回调,如果没有回调这一步直接丢弃IRP数据。
IO完成端口(IO Completion Port)是Windows操作系统的一个内核对象,专门用来解决异步IO的问题,C#中所有异步操作都依赖此端口。其本质是一个发布订阅模式的队列。
CLR在初始化时,创建一个IO Completion Port完成与硬件设备的绑定,使得硬件的驱动程序知道将IRP送到哪里去。
///
/// 创建IO完成端口
///
[DllImport("kernel32.dll")]
static extern nint CreateIoCompletionPort(nint FileHandle, nint ExistingCompletionPort, nint CompletionKey, int NumberOfConcurrentThreads);
// 其他相关API
有兴趣的小伙伴可以玩一玩这个api。
众所周知,Task的底层是ThreadPool,那么答案一定在ThreadPool的源码中。上源码,IOCompletionPoller.Poll
private void Poll()
{
//轮询调用GetQueuedCompletionStatusEx,获取IO数据。
// 其他相关源码
}
//一旦将方法标记为async,编译器就会将代码转换成状态机
static async void Test()
{
// 省略其他代码
}
https://www.cnblogs.com/JulianHuang/p/18137189
https://www.cnblogs.com/huangxincheng/p/13558006.html
分享几个写的不错的博文,偷懒一下。核心是MoveNext函数,里面包含了根据状态机status而执行不同代码的模板代码。一个Task最少要被调用两次MoveNext,第一次调用是主动触发初始化状态机,第二次调用是回调函数再次执行状态机。
public class GetStringAsync : IAsyncStateMachine
{
// 省略其他代码
}
当异步操作发生异常时,IO Completion Port会告诉程序,异步操作已经完成,但存在一个错误。不会跟常规异常一样直接从内核态抛出一个异常。因此ThreadPool会拿到IRP数据,里面包含了异常信息。它自己也不会抛出来。而是调用SetException存储起来。当你调用await/GetResult() 时才会真正的抛出异常。因为当你没有及时获取Task的异常时,它会被丢弃。你需要妥善处理未抛出的异常。
internal TResult GetResultCore(bool waitCompletionNotification)
{
// 省略其他代码
}
在众多异步场景中,有些场景是,GetAsync()第一次需要异步IO等待,然后把结果缓存到静态变量里。接下来N次都是不需要异步IO等待的。直接可以同步完成。比如说Entity Framework中的FindAsync().只有第一次会查询数据库,剩下的N次直接读取内存。如果使用Task
它的出现纯粹为了性能。
public TResult Result
{
get
{
// 省略其他代码
}
}
相关资讯
热门资讯