Асинхронный ReadDirectoryChangesW - GetQueuedCompletionStatus всегда истекает

Точно так же, как это звучит, я пытаюсь асинхронный ReadDirectoryChangesW с IO Completion, и он не работает, в частности, GetLastError многократно возвращает 258 (GetQueuedCompletionStatus Тайм-аут).

У меня есть структуры:

typedef struct dirinfo_struct
{
    HANDLE hDirFH;           // directory handle
    OVERLAPPED Overlapped;   // overlapped storage
    int len_buffer;          // buffer length
    wchar_t* buffer;         // buffer itself
    wchar_t* directory_name; // target name
} dirinfo_t;

typedef struct dirmon_struct
{
    HANDLE hDirOPPort;       // handle to the IO port.
    dirinfo_t* dirinfo;      // pointer to the struct above.
} dirmon_t;

для хранения соответствующей информации. Это инициализировано:

dirinfo_t* t = malloc(1*sizeof(dirinfo_t));
dirmon_t* d = malloc(1*sizeof(dirmon_t));
dirinfo_init(t); // does t->buffer = malloc(8192*sizeof(wchar_t));

Затем я создаю мой каталог и COM-порт:

t->hDirFH = CreateFile(L"C:\\test",
                        FILE_LIST_DIRECTORY,
                        FILE_SHARE_READ|FILE_SHARE_WRITE,
                        NULL,
                        OPEN_EXISTING,
                        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                        NULL); 
d->dirinfo = t;
d->hDirOPPort = CreateIoCompletionPort(d->dirinfo->hDirFH, 
                                       NULL,       
                                       (ULONG_PTR)(d->dirinfo), 
                                       1); 

Затем я передаю эту информацию через d в новый поток. Теперь о новой теме я имею:

bResultQ = GetQueuedCompletionStatus(d->hDirOPPort, lpBytes, 
                                     (ULONG_PTR*)d->dirinfo,    
                                     lpOverlapped, 1000);

if ( bResultQ )
{
    bResultR = ReadDirectoryChangesW(d->dirinfo->hDirFH, 
                                     (void*)d->dirinfo->buffer, 
                                     8192, TRUE,
                                     FILE_NOTIFY_CHANGE_FILE_NAME | 
                                     FILE_NOTIFY_CHANGE_DIR_NAME |
                                     FILE_NOTIFY_CHANGE_ATTRIBUTES | 
                                     FILE_NOTIFY_CHANGE_SIZE |
                                     FILE_NOTIFY_CHANGE_LAST_WRITE | 
                                     FILE_NOTIFY_CHANGE_LAST_ACCESS | 
                                     FILE_NOTIFY_CHANGE_CREATION | 
                                     FILE_NOTIFY_CHANGE_SECURITY,
                                     lpReadDirBytes,
                                     &d->dirinfo->Overlapped,
                                     NULL );
} 
else
{
    printf("GetQueuedCompletionStatus(): Failed, ");
    errorcode = GetLastError();
    printf("Error Code %d\n", errorcode);
    Sleep(500);
}

Поэтому я отключил его и радостно получаю тайм-ауты (258 ошибок), как и должен, так как каталог не изменился. Однако, даже если я изменяю каталог, я все равно получаю сообщения об ошибках; другими словами, эти изменения не обнаруживаются. Что заставляет меня верить, что я настроил это неправильно.

Есть идеи?

Предостережения:

  • По иронии судьбы, это в конечном итоге будет преобразовано в Python через pywin32. Однако, пока я не понимаю, как это должно работать в C, я не пойду туда.
  • синхронный ReadDirectoryChangesW это не вариант. Если никакие события не запускаются, мне нужен поток, для которого истекло время ожидания, чтобы он мог проверить, должен ли он все еще работать.
  • Я пишу специально на C, а не на C++.
  • FindFirstChangeNotification и т.д. тоже не вариант - я не хочу постоянно сравнивать списки каталогов, чтобы выяснить, что изменилось.

Другие заметки:

  • Каталог существует, этот дескриптор не равен NULL. Аналогично для дескриптора компорта.
  • Все передается в поток

Я посмотрел на CDirectoryChangeWatcher из проекта кода, но, используя C++ и многие другие потоки в стороне, я не вижу, что я делаю по-другому. Не стесняйтесь указать это, если я что-то упускаю, хотя!

Вывод, если это помогает, в основном повторяется, независимо от того, насколько я изменяю каталог, о котором идет речь.

GetQueuedCompletionStatus(): Failed, Error Code 258

2 ответа

Решение

Я понимаю, что публикация стен кода обычно считается ужасной, но вот как я это сделал:

Новые структуры:

BOOL runthread;

typedef struct overlapped_struct
{
    OVERLAPPED overlapped;
    wchar_t* buffer;
} overlapped_t;

typedef struct dirinfo_struct
{

    HANDLE hDirOPPort;
    HANDLE hDirFH;
    overlapped_t* o;
    int len_buffer;
    wchar_t* buffer;
    wchar_t* directory_name;
    ULONG_PTR CompletionKey;
} dirinfo_t;

int somekey = 1;

Методы размещения:

void dirinfo_init(dirinfo_t* t)
{
    t->buffer = malloc(16777216*sizeof(wchar_t));
    t->len_buffer = 16777216;
    t->o = calloc(1, sizeof(overlapped_t));
    t->o->buffer = calloc(16777216, sizeof(wchar_t));
    memset(t->o->buffer, 0, 16777216);
    memset(t->o, 0, sizeof(OVERLAPPED));
}

void dirinfo_free(dirinfo_t* t)
{
    free(t->buffer);
    free(t->o->buffer);
    free(t->o);
    free(t);
}

Важные вещи из main() Является ли это:

dirinfo_t* d = malloc(1*sizeof(dirinfo_t));
d->CompletionKey = (ULONG_PTR)&somekey;
dirinfo_init(d);

/* set up */
runthread = TRUE;
d->hDirFH = CreateFile(L"C:\\hydratest",
                FILE_LIST_DIRECTORY,
                FILE_SHARE_READ|FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                NULL); 

d->hDirOPPort = CreateIoCompletionPort(d->hDirFH, NULL, 
                      (ULONG_PTR)d->CompletionKey, 1);  

Тогда наконец моя ожидающая нить. Вот ключ: я не передаю перекрывающуюся структуру. Я передаю структуру, содержащую OVERLAPPED плюс изрядное количество wchar_t на основе хранения. По причинам, которые я не до конца понимаю, это работает. Изменить см. Этот ответ. Я считаю, что область данных здесь действует как перекрывающийся буфер.

DWORD WINAPI WaitingThread(void* args)
{
    DWORD errorcode = 0;    // an error code
    BOOL bResultQ = FALSE;  // obvios=us
    BOOL bResultR = FALSE;
    DWORD NumBytes = 0; 
    FILE_NOTIFY_INFORMATION* pInfo = NULL; // the data incoming is a pointer
                                           // to this struct.
    int i = 0;
    dirinfo_t* d = (dirinfo_t*) args;      // rescue struct from thread arg.

Затем мы попадаем в саму главную нить. Метод проб и ошибок предполагает, что вы должны вызывать как ReadDirectoryW, так и GetQueueCompletionStatus. Я думаю, что это означает, что мы не должны касаться буфера от ReadDirectoryChangeW ** если * нам не сказали, что мы можем GetQueue, Однако исправления этой гипотезы приветствуются.

    while ( runthread )
    {
        bResultR = ReadDirectoryChangesW(d->hDirFH, (void*)d->buffer, 
                                          16777216, TRUE,
               FILE_NOTIFY_CHANGE_FILE_NAME  | FILE_NOTIFY_CHANGE_DIR_NAME |
               FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
               FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | 
               FILE_NOTIFY_CHANGE_CREATION   | FILE_NOTIFY_CHANGE_SECURITY,
                                          NULL,
                                          &d->o->overlapped,
                                          NULL );
        bResultQ = GetQueuedCompletionStatus(d->hDirOPPort, 
                                             &NumBytes, &(d->CompletionKey), 
                                             (LPOVERLAPPED*)(d->o), 1000);

Итак, теперь мы вызвали эти функции, затем мы проверили, что они оба вернули true. большое уродливое предупреждение, если вы правильно настроили свои параметры bResultR всегда возвращает истину, или мне так кажется. bResultQ однако зависит от того, есть ли новые данные на порту.

        if ( bResultQ && bResultR )
        {

Итак, здесь мы приведем этот буфер из ReadDirectoryChangesW и получить доступ к информации из структуры.

            wprintf(L"\n");
            pInfo = (FILE_NOTIFY_INFORMATION*) d->buffer;
            wprintf(L"File %s", pInfo->FileName);
            wprintf(L" changes %d\n", pInfo->Action);
            memset(d->buffer, 0, 16777216);
        }

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

        else
        {
            errorcode = GetLastError();

            if ( errorcode == WAIT_TIMEOUT )
            {
                printf("GetQueuedCompletionStatus(): Timeout\n");
            }
            else
            {
                printf("GetQueuedCompletionStatus(): Failed\n");
                printf("Error Code %d\n", errorcode);
            }
            Sleep(500);
        }
    }   

    return 0;
}

И это завершает то, что я считаю рабочим примером.

Некоторые заметки:

  • Я установил огромный размер буфера. Я заметил, что при копировании примерно 100 файлов в буфере не хватает места 8192 и пропустил пункт или два, здесь и там. Так что я не ожидаю, что это всегда подхватит все. Моим решением было бы сказать каждые 100 событий, убедиться, что дерево файлов - это то, что вы думаете, используя этот метод. Однако бесконечно лучшее решение для постоянного перечисления потенциально большого дерева.

Примечание: чтобы ловить ошибки от GetQueuedCompletionStatus правильно, так как трудно определить, что эта функция действительно возвращена, должно быть сделано следующим образом:

ПРИМЕР:

DWORD dwNumBytes;
ULONG_PTR CompletionKey;
OVERLAPPED* pOverlapped;

//hIOCP is initialized somewhere else in the program
BOOL bOK = GetQueuedCompletionStatus(hIOCP, &dwNumBytes, &CompletionKey, &pOverlapped, 1000);

DWORD dwError = GetLastError();

if(bOK)
{
// Process a successfully completed I/O event.
}
else
{
  if (pOverlapped != NULL)
  {
    // Process a failed completed I/O request
    //dwError contains the reason for failure
  }
  else {
      if (dwError == WAIT_TIMEOUT)
      {
         //Time-out while waiting for completed I/O entry.
      }
      else {
          //Bad call to GetQueuedCompletionStatus
          //dwError contains the reason for the bad call.
      }
}

Пример взят из книги (Windows через C/C++). Пожалуйста, попробуйте реализовать эту обработку ошибок в своем коде.

Также "... темы, которые вызывают GetQueuedCompletionStatus пробуждаются по принципу "первым пришел - первым вышел" (LIFO) ".

ПЕРЕКРЫТИЯ Структура:

При выполнении асинхронного ввода-вывода устройства необходимо передать адрес в инициализированную структуру OVERLAPPED через параметр pOverlapped. Слово "перекрывается" в этом контексте означает, что время, затрачиваемое на выполнение запросов ввода-вывода, перекрывает время, потраченное вашим потоком на выполнение других задач.

Он говорит о параметре, когда вы вызываете ReadFile или WriteFile, как примечание к вышесказанному, которое требует инициализации этой структуры.

Это выглядит следующим образом:

typedef struct _OVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  union {
    struct {
      DWORD Offset;
      DWORD OffsetHigh;
    };
    PVOID  Pointer;
  };
  HANDLE    hEvent;
} OVERLAPPED, *LPOVERLAPPED;

ПРИМЕЧАНИЕ. Вы передаете указатель на структуру dwCompletionKey параметр вашего CreateIoCompletionPort функция. В ссылке, которую я смотрю, они просто передают константу (#define CK_FILE 1) к этому. В нем говорится, что вы можете передавать все, что захотите, так как ОС не волнует. Просто хотел указать на это, однако.

Другие вопросы по тегам