Асинхронный ReadDirectoryChangesW - GetQueuedCompletionStatus всегда время ожидания

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

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

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));

Затем я создаю папку Directory и 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 специально, а не С++.
  • FindFirstChangeNotification и т.д. тоже не вариант. Я не хочу постоянно сравнивать списки каталогов, чтобы определить, что изменилось.

Другие примечания:

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

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

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

GetQueuedCompletionStatus(): Failed, Error Code 258

Ответ 1

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

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

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);

Итак, теперь мы вызываем эти функции, затем проверяем, что оба они вернулись. большое уродливое предупреждение, если у вас есть параметры, настроенные правильно bResultR всегда возвращает true, или так мне кажется. 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);
        }

В противном случае, благодаря Tony для этого, вы можете спокойно игнорировать ошибки 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 событий, проверить, что дерево файлов - это то, что вы думаете, если используете этот метод. Бесконечно лучшее решение, однако, постоянно перечислять потенциально большое дерево.

Ответ 2

Примечание. Чтобы правильно уловить ошибки из 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/С++) Попробуйте выполнить эту обработку ошибок в своем коде.

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

OVERLAPPED Структура:

При выполнении асинхронного устройства I/O, вы должны передать адрес инициализированная структура 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). Он говорит, что вы можете передавать все, что захотите, поскольку ОС не заботится. Просто хотел указать на это.