Как узнать, когда ReadFileEx () завершил ввод-вывод с перекрытием? - PullRequest
2 голосов
/ 05 декабря 2011

HasOverlappedIoCompleted() не работает с асинхронным вводом / выводом, начатым с ReadFileEx() и WriteFileEx(). Фрагмент кода внизу демонстрирует это. В этом примере ReadFileEx() читает из канала, который не имеет ввода, поэтому чтение не будет завершено. Но HasOverlappedIoCompleted() возвращает TRUE. Если я изменяю вызов на перекрывающийся ReadFile(), то HasOverlappedIoCompleted() возвращает FALSE, как и ожидалось.

Мой вопрос: как я могу узнать, завершился ли перекрывающийся запрос ввода-вывода с обратным вызовом, не полагаясь на сам обратный вызов? В моем приложении APC, возможно, был поставлен в очередь, но не обязательно должен еще запускаться, потому что приложение еще не ожидало в состоянии оповещения.

Спасибо.

(Примечание. GetOverlappedResult () не помогает - он также возвращает TRUE.)

Немного больше предыстории: в примере я использую ReadFileEx(), потому что проблему легко продемонстрировать. В моем приложении я звоню WriteFileEx() неоднократно на экземпляре канала. Если предыдущий WriteFileEx() еще не завершен, я должен отбросить сообщение, а не отправлять его (у меня не должно быть более одной ожидающей записи в одном и том же экземпляре канала), но если предыдущий WriteFileEx() завершил , затем я должен запустить следующий, , даже если обратный вызов для завершения еще не запущен .

Редактировать: описание сценария проблемы

  1. Поток переходит в состояние оповещения (с одним прочитанным APC в очереди).
  2. Начинается чтение APC: он ставит в очередь функцию WriteFileEx () и устанавливает флаг ожидания записи. Затем он ставит в очередь ReadFileEx ().
  3. Основной поток начинает работу (без предупреждения).
  4. Чтение в очереди завершено.
  5. Запись в очереди завершается (после чтения).
  6. Основной поток переходит в состояние оповещения.
  7. APC для чтения является первым в очереди, поэтому запускается первым: он просматривает флаг 'отложенной записи' и, поскольку он все еще установлен, сбрасывает запись. На самом деле, хотя WriteFileEx () завершил , он просто еще не вызвал свой APC, потому что ReadFileEx () завершился первым.

Вместо того, чтобы тестировать мой пользовательский флаг 'ожидающих записи', я хочу выяснить в ОС, действительно ли 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;
}

Ответы [ 2 ]

1 голос
/ 24 декабря 2011

Я немного изменил ваш код, чтобы получить 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;
}
1 голос
/ 06 декабря 2011

Мне кажется неправильным поведение, о котором вы просите, потому что оно связано с расой.Эта проблема возникает только в том случае, если вы получаете сообщение A, когда вы все еще отправляете сообщение B. В настоящее время A всегда игнорируется, т. Е. Дополнительное сообщение не отправляется.Поведение, которое вы пытаетесь получить, приведет к тому, что дополнительное сообщение будет отправлено, если и только если сервер был занят обработкой работы в течение интервала между прибытием A и завершением B.Я думаю, что вы должны либо всегда игнорировать A, либо всегда отправлять ответ после завершения B.

Однако вы можете получить такое поведение, если уверены, что это то, что вы хотите.Одним из решений было бы вызвать QueueUserAPC из подпрограммы завершения ReadFileEx, чтобы поставить в очередь вызов функции, которая отправляет ответ.Поскольку APC запускаются в порядке FIFO, новый APC будет определенно работать после того, как APC, которого WriteFileEx уже поставил в очередь.

В зависимости от деталей, для обеих процедур завершения может быть удобнее устанавливать флаги или добавлять элементы вочередь, и ваш основной цикл выполняет фактическую работу.

Если вам нужно опросить APC (например, потому что ваш основной цикл не имеет никаких естественных операций ожидания), вы можете использовать WaitForSingleObjectEx нафиктивное событие с таймаутом 0. Неважно, сигнализировано ли событие или нет, все APC в очереди все равно будут вызываться.

...