C# 在TPL下启动阻塞I/O请求的线程如何立即返回?

C# 在TPL下启动阻塞I/O请求的线程如何立即返回?,c#,.net,multithreading,asynchronous,task-parallel-library,C#,.net,Multithreading,Asynchronous,Task Parallel Library,我想在这个问题的开头说几句话: 我熟悉C#中的wait关键字生成的IAsyncStateMachine实现 我的问题不是关于使用async和wait关键字时确保的基本控制流 假设A 在任何线程环境中,无论是在Windows操作系统级别、POSIX系统还是在.NET线程池中,默认的线程行为都是当线程请求I/O绑定操作(例如磁盘读取)时,它向磁盘设备驱动程序发出请求并进入等待状态。当然,我在掩饰细节,因为它们不是我们讨论的重点 重要的是,该线程在被设备驱动程序通知其完成的中断解除阻塞之前,无法执行任

我想在这个问题的开头说几句话:

  • 我熟悉C#中的
    wait
    关键字生成的
    IAsyncStateMachine
    实现

  • 我的问题不是关于使用
    async
    wait
    关键字时确保的基本控制流

  • 假设A

    在任何线程环境中,无论是在Windows操作系统级别、POSIX系统还是在.NET线程池中,默认的线程行为都是当线程请求I/O绑定操作(例如磁盘读取)时,它向磁盘设备驱动程序发出请求并进入等待状态。当然,我在掩饰细节,因为它们不是我们讨论的重点

    重要的是,该线程在被设备驱动程序通知其完成的中断解除阻塞之前,无法执行任何有用的操作。在此期间,线程将保留在等待队列中,并且不能重新用于任何其他工作

    我想首先确认上述描述

    假设B

    第二,即使引入了TPL,并在.NET framework的v4.5中对其进行了增强,以及对涉及任务的异步操作的语言级支持,假设A中描述的这种默认行为也没有改变

    问题

    然后,我不知所措地试图将假设a和B与突然出现在所有第三方物流文献中的主张相协调:

    当主线程启动此I/O绑定的请求时 工作时,它会立即返回并继续执行剩余的任务 消息泵中已排队的消息

    那么,是什么让那个线程返回去做其他工作呢?该线程不应该在等待队列中处于等待状态吗

    您可能会回答,状态机中的代码启动任务等待器,如果等待器尚未完成,则主线程返回

    这就引出了一个问题——等待者的线索是什么

    脑海中浮现的答案是:无论该方法的实现是什么,它都在等待谁的任务

    这将使我们进一步陷入困境,直到我们到达最后一个实际交付I/O请求的实现

    在.NET framework中,改变线程工作方式的基本机制的部分源代码在哪里

    旁注

    而一些阻止异步方法,如
    WebClient.downloaddatataskancy
    ,如果要遵循它们的代码 通过他们(方法的,而不是自己的)卵圆管进入他们的大脑 肠,你会看到它们最终要么执行 同步下载,如果操作失败,则阻止当前线程 被请求同步执行 (
    Task.RunSynchronously()
    )或者如果异步请求,它们 使用 异步编程模型(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;
    }
    
    公共异步任务GetDataAsync() { var tcs=new TaskCompletionSource(); //如果GetDataInternalAsync发出网络请求 //在与调用线程相同的线程上,它将阻塞,对吗? //那么,他们如何声称线程将立即返回? //如果查看状态机内部,它只会询问TaskWaiter //如果它完成了任务,如果它没有完成,它将注册一个延续 //但这意味着等待者在另一条线上 //那根线在屁股上踢了一脚之前一直在快乐地睡觉 //从一个等待手柄,对吗? //因此,唯一的办法是授权提出请求 //对于线程池线程,在这种情况下,我们并没有真正改进 //可扩展性,但只提高了主/UI线程的响应能力 var s=等待GetDataInternalAsync(); tcs.SetResult;//忽略SetException和 //为简洁起见取消 返回tcs.Task; }
    如果我的问题看起来毫无意义,请对我温和一点。几乎所有事物的知识范围都是有限的。我只是在学习任何东西。

    当您谈论异步I/O操作时,正如Stephen Cleary()在这里指出的那样,事实是没有线程。异步I/O操作在低于线程模型的级别上完成。它通常发生在中断处理程序例程中。因此,没有处理请求的I/O线程

    您询问启动阻塞I/O请求的线程如何立即返回。答案是因为一个I/O请求并不是在它的核心,实际上是阻塞的。您可以阻塞线程,这样您就有意识地说在I/O请求完成之前不要做任何其他事情,但阻塞的绝不是I/O,而是线程决定旋转(或者可能产生它的时间片)

    线程立即返回,因为不必坐在那里轮询或查询I/O操作。这是真正异步的核心。发出I/O请求,最终ISR会弹出完成消息。是的,这可能会冒泡到线程池中以设置任务完成,但这几乎是在不可察觉的时间内发生的