windows 如何判断 ReadFileEx() 重叠 I/O 何时完成?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8387070/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 18:35:35  来源:igfitidea点击:

How to tell when ReadFileEx() overlapped I/O has completed?

windowswinapioverlapped-io

提问by Ian Goldby

HasOverlappedIoCompleted()doesn't work on asynchronous I/O begun with ReadFileEx()and WriteFileEx(). The code snippet at the bottom demonstrates this. In this example, ReadFileEx()reads from a pipe that has no input, so the read will not complete. But HasOverlappedIoCompleted()returns TRUE. If I change the call to an overlapped ReadFile()then HasOverlappedIoCompleted()returns FALSE as expected.

HasOverlappedIoCompleted()不适用于以ReadFileEx()和开头的异步 I/O WriteFileEx()。底部的代码片段演示了这一点。在这个例子中,ReadFileEx()从没有输入的管道中读取,所以读取不会完成。但HasOverlappedIoCompleted()返回 TRUE。如果我改变调用的重叠ReadFile(),然后HasOverlappedIoCompleted()按预期返回FALSE。

My question is: How can I find out whether an overlapped I/O request with callback has completed, without relying on the callback itself? In my application, the APC may have been queued but need not necessarily have run yet because the application may not yet have waited in an alertable state.

我的问题是:如何在不依赖回调本身的情况下找出带有回调的重叠 I/O 请求是否已完成?在我的应用程序中,APC 可能已经排队,但不一定已经运行,因为应用程序可能尚未在警报状态下等待。

Thanks.

谢谢。

(Note GetOverlappedResult() doesn't help - it also returns TRUE.)

(注意 GetOverlappedResult() 没有帮助 - 它也返回 TRUE。)

A bit more background: In the example I'm using ReadFileEx()because it is easy to demonstrate the problem. In my application I am calling WriteFileEx()repeatedly on a pipe instance. If the previous WriteFileEx()has not yet completed I mustdrop the message rather than send it (I must not have more than one pending write on the same pipe instance), but if the previous WriteFileEx()hascompleted then I muststart the next one, even if the completion callback has not yet run.

更多背景信息:在我使用的示例中,ReadFileEx()因为它很容易演示问题。在我的应用程序中,我WriteFileEx()反复调用管道实例。如果前一个WriteFileEx()尚未完成,我必须删除消息而不是发送它(我不能在同一个管道实例上有多个待处理的写入),但是如果前一个WriteFileEx()已经完成,那么我必须开始下一个,即使完成回调尚未运行

Edit: A description of the problem scenario

编辑:问题场景的描述

  1. The thread enters an alertable state (with one read APC queued).
  2. The read APC begins: It queues a WriteFileEx() and sets a 'write pending' flag. It then queues a ReadFileEx().
  3. The main thread begins work (non-alertable).
  4. The queued read completes.
  5. The queued write completes (after the read).
  6. The main thread enters an alertable state.
  7. The read APC is first in the queue so runs first: It looks at the 'write pending' flag and since it is still set it drops the write. In fact though the WriteFileEx() hascompleted, it just hasn't called its APC yet because the ReadFileEx() completed first.
  1. 线程进入警报状态(有一个读取 APC 排队)。
  2. 读取 APC 开始:它将 WriteFileEx() 排队并设置“写入挂起”标志。然后它排队一个 ReadFileEx()。
  3. 主线程开始工作(不可报警)。
  4. 排队读取完成。
  5. 排队的写入完成(读取后)。
  6. 主线程进入警报状态。
  7. 读取 APC 是队列中的第一个,因此首先运行:它查看“写入挂起”标志,并且由于它仍然设置,因此它会丢弃写入。事实上,虽然 WriteFileEx()已经完成,但它只是还没有调用它的 APC,因为 ReadFileEx() 首先完成。

Instead of testing my custom 'write pending' flag, I want to find out from the OS whether the WriteFileEx() has actually completed, even if the APC hasn't yet run.

我不想测试我的自定义“写入挂起”标志,而是想从操作系统中找出 WriteFileEx() 是否实际完成,即使 APC 尚未运行。



#include <Windows.h>
#include <stdio.h>
#include <assert.h>

VOID CALLBACK readComplete(DWORD err, DWORD bytes, LPOVERLAPPED ovlp)
{
}

int main(int argc, char *argv[])
{
  HANDLE     hServer;
  OVERLAPPED serverOvlp = { 0 };
  HANDLE     hClient;
  DWORD      bytes;
  BYTE       buffer[16];
  BOOL       result;

  hServer = CreateNamedPipe("\\.\pipe\testpipe", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
                            PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 
                            PIPE_UNLIMITED_INSTANCES, 0, 0, 5000, NULL);

  serverOvlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

  ConnectNamedPipe(hServer, &serverOvlp);
  assert(GetLastError() == ERROR_IO_PENDING);

  hClient = CreateFile("\\.\pipe\testpipe", GENERIC_READ | GENERIC_WRITE,
                       0, NULL, OPEN_EXISTING, 0, NULL);

  GetOverlappedResult(hServer, &serverOvlp, &bytes, TRUE);

  /* Server starts an overlapped read */
//  result = ReadFile(hServer, buffer, sizeof(buffer), &bytes, &serverOvlp);
  result = ReadFileEx(hServer, buffer, sizeof(buffer), &serverOvlp, readComplete);

  if (HasOverlappedIoCompleted(&serverOvlp))
  {
    puts("Completed");
  }
  else
  {
    puts("Not completed");
  }


  return EXIT_SUCCESS;
}

采纳答案by Harry Johnston

The behaviour you are asking for seems wrong to me, because it involves a race condition. The issue only arises if you receive a message A while you are still sending a message B. At present, A is always ignored, i.e., no additional message is sent. The behaviour you are trying to get would result in an additional message being sent if and only if the server was busy processing work during the interval between A arriving and B completing. I think you should either always be ignoring A or always sending a response once B is completed.

您要求的行为对我来说似乎是错误的,因为它涉及竞争条件。仅当您在发送消息 B 时收到消息 A 时才会出现此问题。目前,A 始终被忽略,即没有发送额外的消息。当且仅当服务器在 A 到达和 B 完成之间的时间间隔内忙于处理工作时,您尝试获得的行为将导致发送额外的消息。我认为您应该始终忽略 A 或在 B 完成后始终发送响应。

However, you can get this behaviour if you're sure it is what you want. One solution would be to call QueueUserAPC from the ReadFileEx completion routine, to queue a call to a function which sends the reply. Since APCs are run in FIFO order, the new APC will definitely run after the APC that WriteFileEx has already queued.

但是,如果您确定这是您想要的,则可以获得此行为。一种解决方案是从 ReadFileEx 完成例程中调用 QueueUserAPC,以对发送回复的函数的调用进行排队。由于 APC 是按 FIFO 顺序运行的,因此新的 APC 肯定会在 WriteFileEx 已经排队的 APC 之后运行。

Depending on the details, it might be cleaner for both completion routines to set flags, or add items to a queue, and have your main loop do the actual work.

根据详细信息,两个完成例程设置标志或将项目添加到队列并让您的主循环执行实际工作可能会更清晰。

If you need to poll for APCs (for example, because your main loop doesn't have any naturally occurring wait operations) you can use WaitForSingleObjectEx on a dummy event with a timeout of 0. It doesn't matter whether the event is signaled or not, all queued APCs will still be called.

如果您需要轮询 APC(例如,因为您的主循环没有任何自然发生的等待操作),您可以在超时为 0 的虚拟事件上使用 WaitForSingleObjectEx。不是,所有排队的 APC 仍将被调用。

回答by Lucian

I've changed your code a bit to achieve the CALLBACK. For this example to work, the pipe buffer length should not be 0. Also, I think that both ends of the pipe should have the OVERLAPPED flag set.

我已经稍微更改了您的代码以实现 CALLBACK。为了让这个例子起作用,管道缓冲区长度不应该是 0。另外,我认为管道的两端都应该设置 OVERLAPPED 标志。

#include <Windows.h>
#include <stdio.h>
#include <assert.h>

#define BUFFSIZE 100
#define MYPIPE   "\\.\pipe\testpipe"

typedef struct {
  OVERLAPPED serverOvlp;        // Overlapped should always be first in structure
  CHAR       buffer[20];
} OVLP;


VOID CALLBACK readComplete(DWORD err, DWORD bytes, LPOVERLAPPED ovlp)
{
  OVLP *temp = (OVLP *) ovlp;
  printf("readComplete  err=%d  bytes=%d  buffer=%s\n", err, bytes, temp->buffer);
}

int main(void)
{
  HANDLE     hServer;
  HANDLE     hClient;

  OVLP       oServer;

  DWORD      bytes;
  CHAR       ClientBuffer[20] = "Test message";

  hServer = CreateNamedPipe(MYPIPE, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
                            PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                            PIPE_UNLIMITED_INSTANCES, BUFFSIZE, BUFFSIZE, 5000, NULL);

//-------------------------------------- CLIENT 
  hClient = CreateFile(MYPIPE, GENERIC_READ | GENERIC_WRITE,
                       0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

  WriteFile(hClient,ClientBuffer,strlen(ClientBuffer)+1,&bytes,NULL);

//-------------------------------------- SERVER
  ConnectNamedPipe(hServer, &oServer.serverOvlp);
  if (HasOverlappedIoCompleted(&oServer.serverOvlp)) assert(GetLastError() != 0 );
  puts("Client Pipe connected\n");

  ReadFileEx(hServer, oServer.buffer, sizeof(oServer.buffer), (LPOVERLAPPED)&oServer, readComplete);

  SleepEx(INFINITE,TRUE);       // Creates an alertable event so CALLBACK is triggered

  if (HasOverlappedIoCompleted(&oServer.serverOvlp)) {
    puts("Completed");
  } else {
    puts("Not completed");
  }

  return EXIT_SUCCESS;
}