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

时间:2014-01-06 06:06:54

标签: 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操作回调是否允许在前一个回调仍处于执行状态时同时重新进入?(来自我的有限测试,答案是否)以及为什么?

2 个答案:

答案 0 :(得分:2)

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

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

第一个代码片段非常糟糕,我很难找到任何理由。 sleep()循环轮询在响应输入时具有内置的平均50ms延迟。如果存在更好的同步和异步解决方案,那就太过分了。专用的读线程,排队的APC,(完成例程)和IOCP都可用于串行端口。

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

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

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

2)IOCP处理程序线程在等待完成消息时阻塞,当然。如果没有任何事情要做,线程应该阻止:)

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

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

答案 1 :(得分:0)

  1. 可能是因为异步编程很难。
  2. .Net,在I / O方法上,它主要暴露同步操作和异步操作。例如,您有TcpClient.ConnectTcpClient.ConnectAsync / TcpClient.BeginConnect。无论以“Begin”开头还是以“Async”结尾,至少应该是异步,这意味着没有被阻塞的线程。 TcpClient.Connect阻塞,因此可扩展性较低。
  3. 我不太确定,但我认为他们可以。问题是你为什么要这样做?如何将回调与其通话相匹配?