我想用以下内容作为前言:
我熟悉C#中IAsyncStateMachine
关键字生成的await
实现。
我的问题与基本控制流有关,确保您何时使用async
和await
关键字。
假设A
任何线程环境中的默认线程行为,无论是在Windows操作系统级别还是在POSIX系统中,还是在.NET线程池中,都是当线程发出I / O绑定操作请求时,对于磁盘读取,它会向磁盘设备驱动程序发出请求,并且进入等待状态。当然,我正在掩饰细节,因为它们不是我们讨论的时刻。
重要的是,该线程在被来自设备驱动程序的中断解除阻塞之前无法执行任何操作。在此期间,该线程仍保留在等待队列上,并且不能重新用于任何其他工作。
我首先要确认上述说明。
假设B
其次,即使引入了TPL,并且在.NET框架的v4.5中进行了增强,并且对涉及任务的异步操作的语言级支持,假设A中描述的这种默认行为没有改变。
问题
然后,我不知所措,试图将假设A和B 与所有TPL文献中突然出现的主张相协调:
当主线程启动此I / O绑定请求时 工作,立即返回并继续执行剩下的工作 消息泵中的排队消息。
那么,是什么让该线程返回回去做其他工作?该线程是否应该在等待队列中的等待状态?
您可能会想回复状态机中的代码启动任务等待,如果等待者没有完成,主线程将返回。
这样就乞求了问题 - awaiter在哪个线程上运行?
理所当然的答案是:无论方法的实施是什么,它的任务都在等待。
这让我们进一步推动了兔子洞,直到我们到达最后一个实际提供I / O请求的实现。
.NET框架中源代码的哪一部分改变了关于线程如何工作的基本机制?
旁注
虽然有些阻止异步方法,例如
WebClient.DownloadDataTaskAsync
,但如果有人遵循他们的代码 通过他们(方法而不是自己的方法)的椭圆形进入他们的 肠子,人们会看到他们最终要么执行 同步下载,如果操作阻塞当前线程 被要求同步进行 (Task.RunSynchronously()
)或者如果异步请求它们 使用。将阻塞I / O绑定调用卸载到线程池线程 异步编程模型(APM)Begin
和End
方法。这肯定会导致主线程立即返回,因为 它只是将阻塞I / O工作卸载到线程池线程,从而 将大约diddlysquat添加到应用程序的可伸缩性中。
但这是一个案例,在野兽的内部,工作 被秘密卸载到线程池线程。在API的情况下 这并不是那样做的,比如说这样的API:
public async Task<string> GetDataAsync() { var tcs = new TaskCompletionSource<string>(); // If GetDataInternalAsync makes the network request // on the same thread as the calling thread, it will block, right? // How then do they claim that the thread will return immediately? // If you look inside the state machine, it just asks the TaskAwaiter // if it completed the task, and if it hasn't it registers a continuation // and comes back. But that implies that the awaiter is on another thread // and that thread is happily sleeping until it gets a kick in the butt // from a wait handle, right? // So, the only way would be to delegate the making of the request // to a thread pool thread, in which case, we have not really improved // scalability but only improved responsiveness of the main/UI thread var s = await GetDataInternalAsync(); tcs.SetResult(s); // omitting SetException and // cancellation for the sake of brevity return tcs.Task; }
如果我的问题似乎是荒谬的,请对我温柔。几乎所有事情对事物的了解程度都是有限的。我正在学习任何东西。
答案 0 :(得分:2)
当你在讨论异步I / O操作时,正如Stephen Cleary(http://blog.stephencleary.com/2013/11/there-is-no-thread.html)所指出的那样,事实上没有线程。异步I / O操作在比线程模型更低的级别完成。它通常发生在中断处理程序例程中。因此,没有I / O线程处理请求。
您询问启动阻塞I / O请求的线程如何立即返回。答案是因为I / O请求实际上并未阻塞。您可以阻止一个线程,以便在I / O请求完成之前故意说不要做任何其他操作,但它从来就不是阻塞的I / O,而是线程决定旋转(或者可能产生其时间片) )。
线程立即返回,因为没有必要坐在那里轮询或查询I / O操作。这是真正的异步性的核心。发出I / O请求,最终完成从ISR中冒出。是的,这可能会冒泡进入线程池以设置任务完成,但这种情况在几乎难以察觉的时间内发生。工作本身永远不必在线程上运行。请求本身可能是从线程发出的,但由于它是异步请求,因此线程可以立即返回。
让我们暂时忘记C#。假设我正在编写一些嵌入式代码,并且我从SPI总线请求数据。我发送请求,继续我的主循环,当SPI数据准备就绪时,触发ISR。我的主循环立即恢复,因为我的请求是异步的。它所要做的就是将一些数据推入移位寄存器并继续。当数据准备好让我回读时,会触发中断。这不是在线程上运行。它可能会中断一个线程来完成ISR,但你不能说它实际上是在那个线程上运行的。仅仅因为它的C#,这个过程最终没有任何不同。
同样,假设我想通过USB传输数据。我将数据放在DMA位置,设置一个标志告诉总线转移我的URB,然后立即返回。当我收到回复时,它也被移入内存,发生中断并设置一个标志让系统知道嘿,下载一个数据包就在你的缓冲区中。
再一次,I / O永远不会真正阻止。它似乎可以阻止,但这不是低级别的情况。更高级别的进程可能决定I / O操作必须与其他代码同步发生。这并不是说I / O是即时的。只是CPU没有停止工作来维护I / O.如果以这种方式实现它可能会阻塞,并且这可能涉及线程。但这不是异步I / O的实现方式。