如何找出 CancelIo() 何时完成?

Posted

技术标签:

【中文标题】如何找出 CancelIo() 何时完成?【英文标题】:How to find out when CancelIo() is done? 【发布时间】:2010-10-13 05:51:21 【问题描述】:

CancelIo() 应该取消与调用线程关联的所有待处理 I/O 操作。根据我的经验,CancelIo() 有时也会取消未来 I/O 操作。给定:

ReadFile(port, buffer, length, &bytesTransferred, overlapped);
    如果我在读取之前立即调用CancelIo(port)GetQueuedCompletionStatus() 将永远阻塞,永远不会收到读取操作。 如果我在读取后立即调用 CancelIo(port)GetQueuedCompletionStatus() 将返回 0 和 GetLastError()==ERROR_OPERATION_ABORTED 如果我调用 CancelIo(port) 并且没有挂起或后续读取,GetQueuedCompletionStatus() 将永远阻塞。

这里的关键是没有办法检测CancelIo() 何时执行完毕。如何确保CancelIo() 已完成执行并且可以安全地发出进一步的读取请求?

PS:看看http://osdir.com/ml/lib.boost.asio.user/2008-02/msg00074.html 和http://www.boost.org/doc/libs/1_44_0/doc/html/boost_asio/using.html 听起来 CancelIo() 并不是真的可用。必须客户需要 Windows XP 支持。我有哪些选择?

注意:我正在从串行端口读取数据。

【问题讨论】:

【参考方案1】:

CancelIo() 工作正常。我误解了我的代码。

经过进一步调查,结果发现代码调用了CancelIo(),然后是ReadFile(),超时INFINITE。完成端口从未收到读取通知,因为远程端从未发送任何内容。也就是说CancelIo()没有取消后续操作。

我发现了一些令人大开眼界的文档here:

为异步 I/O 编码时要小心,因为系统保留在需要时使操作同步的权利。因此,最好编写程序以正确处理可能同步或异步完成的 I/O 操作。示例代码演示了这种考虑。

事实证明,如果正在读取的数据已被设备驱动程序缓存,则设备驱动程序可能会选择以同步方式处理异步操作。经过进一步调查,我发现当CancelIo()ReadFile() 之前被调用时,有时会导致后者同步返回。我不知道为什么完成端口在CancelIo() 之后从未收到ReadFile() 的通知,但我无法再重现此问题。

无论ReadFile() 是同步的还是异步的,都会发出完成端口的信号。

【讨论】:

我的回答中的建议顺便提到了这一点。 I/O 的同步完成仍会导致overlapped.Handle 发出信号。 我花了几个小时试图理解为什么取消后HasOverlappedIoCompleted 仍然是错误的,感谢@BenVoigt 和您的澄清。等待overlapped.hEvent 表明 iocp 已发出信号。【参考方案2】:

等待(可能超时为零)overlapped.Handle。将设置操作是完成还是取消。

【讨论】:

msdn.microsoft.com/en-us/library/aa363791%28VS.85%29.aspx 说“CancelIO 只取消句柄上未完成的 I/O,它不会改变句柄的状态;这意味着你不能依赖句柄的状态,因为你不知道操作是成功完成还是取消。” @Gili:文件 HANDLE 与重叠结构中的事件 HANDLE 不同。该文本是指文件 HANDLE。【参考方案3】:

如果您已经在使用重叠操作,为什么还需要取消 I/O? “取消”正在进行的 I/O 操作的整个概念真的容易竞争,并且完全受制于您尝试写入的底层设备堆栈;实际上,您唯一想要这样做的就是解除对等待该 I/O 完成的另一个线程的阻塞。

【讨论】:

想象一下我正在读/写套接字。线程 1 不写任何东西,但会尝试超时读取。一旦发生超时,线程 2 将写入套接字并尝试读取响应。如果我从不取消线程 1 的操作,它将“窃取”应该转到线程 2 的字节。 谁在等待,这是一个异步操作 - 如果您不想要它,只需让完成例程忽略结果。我想我明白你关于飞行中不需要的 I/O 会妨碍未来 I/O 的观点,但我仍然认为这不是一个大问题(编辑:哎呀,上面的评论在我写回复时发生了变化) @Gili 嗯,我想我明白你现在在说什么了。尽管我认为将您的应用程序设计为具有这样的线程亲和性是一个坏主意 - 最好假设完成回调在 arbitrary 线程上返回,然后将结果分派到正确的位置。总的来说,你有一个非常鼓舞人心的例子。 此外,考虑传递到 ReadFile() 的缓冲区必须在读取操作期间保持有效这一事实。我在 ReadFile() 之上提供了一个 API。这意味着从最终用户的角度来看,他需要在读取操作范围之外保持缓冲区有效(即,当他取消它时,他认为读取已经结束)。他不清楚何时可以安全地回收缓冲区。 内核驱动程序处理这个的方式是通过状态机,你有“等待响应 X”状态,然后你处理和解释你得到的任何响应,不管你在哪个线程。 【参考方案4】:

可以编写没有 CancelIo 函数的异步 I/O 代码。问题取决于您使用 CancelIO 的场景。假设您需要实现文件读取线程。线程伪代码:

为了(;;) ReadFile(端口, 缓冲区, 长度, &bytesTransferred, 重叠); WaitForMultipleObjects(重叠事件+停止事件); 如果(停止事件发出信号) 休息; 如果(重叠的事件发出信号) 处理 ReadFile 结果

此类线程使用重叠 I/O 读取文件(套接字、端口等)。大多数时候它在 WiatForMultipleObjects 线上等待。它在有新数据可用或发出停止事件信号时唤醒。要停止此线程,请从另一个线程设置停止事件。没有使用 CancelIO。

【讨论】:

是否可以中止读取并继续使用句柄而不关闭它? 您不需要中止读取操作,只需停止等待重叠事件即可。例如,您可以等待带有超时的重叠事件。超时后没有数据被读取,你可以做你需要的一切,文件句柄是有效的。 想象一下我正在读/写套接字。线程 1 不写任何东西,但会尝试超时读取。一旦发生超时,线程 2 将写入套接字并尝试读取响应。如果我从不取消线程 1 的操作,它将“窃取”应该转到线程 2 的字节。 -1,这是完全错误的。您不能让重叠的操作以这种方式悬垂。一旦 ReadFile 完成,它会将数据写入缓冲区(甚至可能不再存在)并更新重叠结构(再次,可能不再存在)。您必须始终等待操作完成——调用CancelIo 将使操作提前完成。 @AlexFarber,差不多,在调用CancelIo之后,还需要等待操作完成。如果操作被取消(它不是必须的,因为它可以在你调用 CancelIo 之前完成,或者负责文件的驱动程序不需要支持取消),GetOverlappedResult 将返回 ERROR_CANCELLED。跨度>

以上是关于如何找出 CancelIo() 何时完成?的主要内容,如果未能解决你的问题,请参考以下文章

您如何判断用户何时滚动到 LazyVGrid 的底部?

如何找出截断的 UILabel 文本的宽度

重叠 I/O:如何在完成端口事件或正常事件上唤醒线程?

使用Paramiko单独执行多个相关命令,并找出每个命令何时完成

完成端口怎么优雅地关闭socket

writeToFile 如何判断何时完成