c# - I/O 操作的 .NET 基于事件的异步模式是否会阻塞底层线程?

标签 c# c++ multithreading asynchronous threadpool

在典型的 .NET 世界中,我们对大多数 I/O 操作使用基于事件的异步模式(事件处理程序),据我所知,更具体地说,引入了 I/O 完成端口以提高线程调度效率,像ThreadPool,因此我们不需要手动维护(初始化和销毁​​)线程来处理大量的I/O响应。

与此同时,我很自然地认为等待 I/O 响应不需要在现代 Windows 系统中因为硬件中断而阻塞任何线程,直到我在最近的项目中看到了一些 C++ 代码,甚至在 web 中看到了一些示例代码。

我没有任何 C++ 经验

第一段代码是关于串口监听的,伪C++代码(我输入的是C#风格)是这样的:

    // loop checking the status
    while(serialPort.Buffer.Count==0)
    {
          Thread.Sleep(100);
    }  

    byte[] data = serialPort.Buffer;
    // processing the actual data...

第二段代码是关于C++中I/O完成端口的使用:

    while (::GetQueuedCompletionStatus(port,
                                       &bytesCopied,
                                       &completionKey,
                                       &overlapped,
                                       INFINITE))
    {
        if (0 == bytesCopied && 0 == completionKey && 0 == overlapped)
        {
            break;
        }
        else
        {
            // Process completion packet
        }
    }

很明显,他们都阻塞了线程。

所以我的问题是:

  1. 为什么那些代码没有选择基于事件的无线程阻塞方式?

  2. 如果.NET底层使用第二个示例的代码,那么在做I/O操作时实际上有线程被阻塞?

  3. (可能有点跑题).NET I/O操作回调是否允许在之前的回调还在执行的时候并发重入?(根据我有限的测试,答案是否定的),为什么?

最佳答案

首先,阻塞本身并不是坏事。 Windows 应用程序中的“主”GUI 线程会触发其“OnClick”等事件,以响应从 Windows 消息队列(一个阻塞的生产者-消费者队列)接收到的消息。当没有收到消息时,线程阻塞在队列中。与大多数“非阻塞”基于 select() 的服务器一样 - select 是一个阻塞调用,(虽然它可以通过设置低/零超时来进行轮询 - 一个糟糕的设计)。

1) 异步设计本质上更复杂。每个套接字上下文数据(例如缓冲区)不能在基于堆栈的自动变量中维护,必须通过维护上下文对象的全局容器(必须由事件中的套接字句柄查找)来跨事件维护当它们被触发时),或者通过 I/O 请求发出上下文对象并从事件中的回调参数中检索它们。异步设计应该是完全异步的——如果可能的话,必须避免调用任何可能阻塞任何延长时间的东西。调用不透明的外部库、数据库查询等在这方面可能会很麻烦,阻塞假定的异步线程并阻止它响应事件。

第一个代码片段太可​​怕了,我很难为它找到任何理由。 sleep() 循环轮询在响应输入时具有内置的平均 50 毫秒延迟。当存在更好的同步和异步解决方案时,这只是 mega-lame。专用读取线程、排队的 APC、(完成例程)和 IOCP 都可用于串行端口。

第二个代码片段实际上是基于事件的异步。通过让处理程序线程使用完成消息返回的参数调用事件处理程序,您可以使它看起来更加“基于事件”。

IOCP 是 Windows 的首选高性能 I/O 系统。它可以处理许多类型的 I/O 操作,并且它基于线程池的处理程序可以承受偶尔的阻塞或冗长的操作,而不会阻止进一步 I/O 完成的处理。通过调用传递用户缓冲区允许驱动程序将它们直接加载到内核空间并删除一层复制。它不做的是避免在异步调用中维护上下文的需要。

每个客户端的同步线程通常用于可伸缩性要求被简单的内联代码淹没的情况,并且不受此类设计中固有的阻塞调用的影响。处理串行通信并不是可扩展到数千个端口的问题。

2) 当然,IOCP 处理程序线程在等待完成消息时阻塞。如果无事可做,线程应该阻塞:)

3) 他们应该这样做。添加一个额外的信号层以确保回调被串行处理涉及更多的开销,并将漏洞添加到回调中的任何类型的阻塞,阻止处理来自不需要阻塞的其他 IOCP 处理程序线程的其他回调.由于上下文作为参数传入,因此没有内在要求以串行方式运行 IOCP 驱动的回调。回调处理程序中的代码可以仅以状态机的方式对传递的信息进行操作。

也就是说,如果 MS .NET 确实提供了信号/队列来强制执行串行的、不可重入的回调,我不会感到惊讶。开发人员经验不足。经常在多线程回调中做他们不应该做的事情,例如。无需任何锁定即可访问全局/持久状态,或直接访问线程绑定(bind)的 GUI 控件。将调用序列化(通过将它们包装到 Windows 消息中或以其他方式)以牺牲性能为代价消除了这种风险。

关于c# - I/O 操作的 .NET 基于事件的异步模式是否会阻塞底层线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20944013/

相关文章:

c# - 如何将数据写入 ASP.NET MVC 中的 App_Data 文件夹?

c++ - C++ 中的 23 位用户定义类型

java - 为什么线程数增加时程序变慢

c# - 将范围索引添加到 Azure DocumentDB 集合时出现异常

c# - 在没有 SetSPN 的情况下查询/更改 Windows 域上的 SPN

c# - 如何将 SqlBulkCopy 与 DataTable 中的二进制数据 (byte[]) 一起使用?

c++ - do-while 中的输入验证

c++ - 错误显式类型丢失 ('int assumed' )

linux - 使用 kernel_thread 创建内核线程

java - 无内存屏障的乱序写入 : the only possible cause of Data Race?