2016-11-14 5 views
0

ВВЕДЕНИЕ:

Я пытаюсь использовать ReadDirectoryChangesW асинхронно в цикле.Использование ReadDirectoryChangesW асинхронно в цикле

Ниже фрагмент кода иллюстрирует то, что я пытаюсь достичь:

DWORD example() 
{ 
    DWORD error = 0; 

    OVERLAPPED ovl = { 0 }; 
    ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); 

    if (NULL == ovl.hEvent) return ::GetLastError(); 

    char buffer[1024]; 

    while(1) 
    { 
     process_list_of_existing_files(); 

     error = ::ReadDirectoryChangesW(
      m_hDirectory, // I have added FILE_FLAG_OVERLAPPED in CreateFile 
      buffer, sizeof(buffer), FALSE, 
      FILE_NOTIFY_CHANGE_FILE_NAME, 
      NULL, &ovl, NULL); 

     // we have new files, append them to the list 
     if(error) append_new_files_to_the_list(buffer); 
     // just continue with the loop 
     else if(::GetLastError() == ERROR_IO_PENDING) continue; 
     // RDCW error, this is critical -> exit 
     else return ::GetLastError(); 
    } 
} 

ПРОБЛЕМА:

Я не знаю, как обращаться со случаем, когда ReadDirectoryChangesW возвращается FALSE с GetLastError() кодом быть ERROR_IO_PENDING.

В этом случае я должен просто продолжить цикл и продолжать цикл до тех пор, пока ReadDirectoryChangesW не вернет buffer Я могу обработать.

МОИ ПОПЫТКИ Чтобы решить эту проблему:

Я попытался с помощью WaitForSingleObject(ovl.hEvent, 1000), но он выходит из строя с ошибкой 1450 ERROR_NO_SYSTEM_RESOURCES. Ниже MVCE, который воспроизводит это поведение:

#include <iostream> 
#include <Windows.h> 

DWORD processDirectoryChanges(const char *buffer) 
{ 
    DWORD offset = 0; 
    char fileName[MAX_PATH] = ""; 
    FILE_NOTIFY_INFORMATION *fni = NULL; 

    do 
    { 
     fni = (FILE_NOTIFY_INFORMATION*)(&buffer[offset]); 
     // since we do not use UNICODE, 
     // we must convert fni->FileName from UNICODE to multibyte 
     int ret = ::WideCharToMultiByte(CP_ACP, 0, fni->FileName, 
      fni->FileNameLength/sizeof(WCHAR), 
      fileName, sizeof(fileName), NULL, NULL); 

     switch (fni->Action) 
     { 
     case FILE_ACTION_ADDED:  
     { 
      std::cout << fileName << std::endl; 
     } 
     break; 
     default: 
      break; 
     } 

     ::memset(fileName, '\0', sizeof(fileName)); 
     offset += fni->NextEntryOffset; 

    } while (fni->NextEntryOffset != 0); 

    return 0; 
} 

int main() 
{ 
    HANDLE hDir = ::CreateFile("C:\\Users\\nenad.smiljkovic\\Desktop\\test", 
     FILE_LIST_DIRECTORY, 
     FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
     NULL, OPEN_EXISTING, 
     FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); 

    if (INVALID_HANDLE_VALUE == hDir) return ::GetLastError(); 

    OVERLAPPED ovl = { 0 }; 
    ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); 

    if (NULL == ovl.hEvent) return ::GetLastError(); 

    DWORD error = 0, br; 
    char buffer[1024]; 

    while (1) 
    { 
     error = ::ReadDirectoryChangesW(hDir, 
      buffer, sizeof(buffer), FALSE, 
      FILE_NOTIFY_CHANGE_FILE_NAME, 
      NULL, &ovl, NULL); 

     if (0 == error) 
     { 
      error = ::GetLastError(); 

      if (ERROR_IO_PENDING != error) 
      { 
       ::CloseHandle(ovl.hEvent); 
       ::CloseHandle(hDir); 
       return error; 
      } 
     } 

     error = ::WaitForSingleObject(ovl.hEvent, 0); 

     switch (error) 
     { 
     case WAIT_TIMEOUT: 
      break; 
     case WAIT_OBJECT_0: 
     { 
      error = processDirectoryChanges(buffer); 

      if (error > 0) 
      { 
       ::CloseHandle(ovl.hEvent); 
       ::CloseHandle(hDir); 
       return error; 
      } 

      if (0 == ::ResetEvent(ovl.hEvent)) 
      { 
       error = ::GetLastError(); 
       ::CloseHandle(ovl.hEvent); 
       ::CloseHandle(hDir); 
       return error; 
      } 
     } 
     break; 
     default: 
      error = ::GetLastError(); 
      ::CloseHandle(ovl.hEvent); 
      ::CloseHandle(hDir); 
      return error; 
      break; 
     } 
    } 

    return 0; 
} 

Читая документацию, мне кажется, что мне нужно GetOverlappedResult с последним параметром, установленным в FALSE, но я не знаю, как использовать этот API должным образом.

ВОПРОС:

Поскольку MVCE очень хорошо иллюстрирует то, что я пытаюсь сделать (напечатать имена недавно добавленных файлов), вы можете показать мне, что должно быть зафиксировано в цикле while для того, чтобы Работа?

Опять же, необходимо использовать асинхронно ReadDirectoryChangesW в цикле, как показано в фрагменте из ВВЕДЕНИЯ.

+0

Этот код не имеет большого смысла. Он взрывается, потому что вы используете 0 тайм-аут вместо INFINITE, поэтому вы на самом деле не ожидаете и вызываете ReadDirectoryChangesW с очень высокой скоростью. Только когда-либо используйте OVERLAPPED, если у вас есть что-то еще. Таким образом, вы можете использовать WaitForMultipleObjects(). Как указано, вы не должны его использовать. –

+0

@HansPassant: Если RDCW не возвращает буфер, который я могу использовать, я бы хотел продолжить цикл, выполняя другой код ('foo()' сверху), пока не получу заполненный буфер. Это был момент, когда я ждал 0 секунд. Если ничего не доступно, цикл продолжается, иначе я обрабатываю содержимое буфера. Я извиняюсь за путаницу, я не являюсь носителем английского языка, поэтому я понимаю, что мое намерение не ясно при первом чтении. – AlwaysLearningNewStuff

+5

@AlwaysLearningNewStuff, если RDCW возвращает ошибку ожидания ввода-вывода, просто не вызывайте RDCW снова до тех пор, пока не будет сигнализировано о перекрывшемся событии. Тем временем вы можете продолжать цикл и делать другие вещи.В показанном коде вызовите RDCW один раз перед входом в цикл, а затем снова вызовите его в обработчике 'WAIT_OBJECT_0'. –

ответ

3

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

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

(Кроме того, вы должны называть GetOverlappedResult, чтобы проверить, был ли I/O успешно или нет.)

Таким образом, ваш цикл должен выглядеть следующим образом:

::ReadDirectoryChangesW(hDir, 
     buffer, sizeof(buffer), FALSE, 
     FILE_NOTIFY_CHANGE_FILE_NAME, 
     NULL, &ovl, NULL); 

    while (1) 
    { 
     DWORD result = ::WaitForSingleObject(ovl.hEvent, 0); 

     switch (result) 
     { 
     case WAIT_TIMEOUT: 

      processBackgroundTasks(); 

      break; 

     case WAIT_OBJECT_0: 

      ::GetOverlappedResult(hDir, &ovl, &dw, FALSE); 

      processDirectoryChanges(buffer); 

      ::ResetEvent(ovl.hEvent); 

      ::ReadDirectoryChangesW(hDir, 
       buffer, sizeof(buffer), FALSE, 
       FILE_NOTIFY_CHANGE_FILE_NAME, 
       NULL, &ovl, NULL); 

      break; 
     } 
    } 

Примечания:

  • Обработка ошибок устранена для простоты; Я не проводил никаких проверок или не проверял ваш код для каких-либо других проблем.

  • Если нет каких-либо фоновых задач для выполнения, вы должны проверить это событие и установить тайм-аут на INFINITE, а не 0, когда это произойдет, иначе вы будете вращаться.

  • Я хотел показать только минимальные изменения, необходимые для его работы, но вызов WaitForSingleObject, за которым следует GetOverlappedResult, избыточен; один вызов GetOverlappedResult может проверить, завершен ли ввод-вывод и получить результаты, если это так.


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

::ResetEvent(ovl.hEvent); 

    if (!::ReadDirectoryChangesW(hDir, 
     buffer, sizeof(buffer), FALSE, 
     FILE_NOTIFY_CHANGE_FILE_NAME, 
     NULL, &ovl, NULL)) 
    { 
     error = GetLastError(); 
     if (error != ERROR_IO_PENDING) fail(); 
    } 

    while (1) 
    { 
     BOOL wait; 

     result = process_list_of_existing_files(); 

     if (result == MORE_WORK_PENDING) 
     { 
      wait = FALSE; 
     } 
     else if (result == NO_MORE_WORK_PENDING) 
     { 
      wait = TRUE; 
     } 

     if (!::GetOverlappedResult(hDir, &ovl, &dw, wait)) 
     { 
      error = GetLastError(); 
      if (error == ERROR_IO_INCOMPLETE) continue; 
      fail(); 
     } 

     processDirectoryChanges(buffer); 

     ::ResetEvent(ovl.hEvent); 

     if (!::ReadDirectoryChangesW(hDir, 
      buffer, sizeof(buffer), FALSE, 
      FILE_NOTIFY_CHANGE_FILE_NAME, 
      NULL, &ovl, NULL)) 
     { 
      error = GetLastError(); 
      if (error != ERROR_IO_PENDING) fail(); 
     } 
    } 
+0

вызывать ReadDirectoryChangesW асинхронно, а затем WaitForSingleObject - почти убить асинхронное поведение. только одно преимущество этого типа цикла - 'processBackgroundTasks();' кроме этого - мы можем вызвать ReadDirectoryChangesW синхронно с тем же эффектом. но похож на главную проблему OP - он не понимает, как работает асинхронная работа, как привязать контекст к работе, а затем использовать в обратных вызовах. – RbMm

+0

Благодарим вас за ответ. Что касается вашего второго наблюдения, цель, которую я хочу достичь, показана в фрагменте кода в ВВЕДЕНИИ. Что касается третьего наблюдения, можете ли вы отредактировать свой ответ, чтобы показать, как правильно использовать «GetOverlappedResult»? Я бы очень хотел сделать это правильно. Что касается проверки ошибок, мне нужно обработать их, поэтому, если вы могли бы по-прежнему обрабатывать ERROR_IO_PENDING, что должно быть достаточно. Спасибо за ответ. – AlwaysLearningNewStuff

+0

Без описания того, что 'process_list_of_existing_files();' на самом деле, этот фрагмент не очень полезен. Если будет * всегда * работать, вам не нужно беспокоиться о вращении, просто это кажется маловероятным - конечно, если пользователь перестанет добавлять новые файлы, вы в конечном итоге закончите то, что вы делаете на существующие? –

1

вариант косвенного использования IOCP

  1. создать класс/структура наследуется (содержащие) ПЕРЕКРЫВАЕТСЯ (или IO_STATUS_BLOCK), счетчик ссылок, каталог ручки и данные, которые вам нужно
  2. вызов BindIoCompletionCallback (RtlSetIoCompletionCallback) для ручка каталога для настройки вашего обратного вызова
  3. есть DoRead() рутины, который мы первый вызов времени от основного потока , а затем от обратного вызова
  4. в DoRead() перед каждым вызовом ReadDirectoryChangesW вызова AddRef();, потому что мы передаем ссылку (через OVERLAPPED) к нашей структуре ядро ​​
  5. главного (скажем, GUI нить) может продолжить выполнение собственной задачи после первоначального вызова DoRead(), в отличие от варианта APC ему не нужно ждать в аварийном состоянии
  6. В обратном вызове мы получили указатель на нашу структуру из унаследованного (содержащего) OVERLAPPED. делать какие-либо задачи (processDirectoryChanges), в случае необходимости продолжать шпионить - вызов DoRead() и, наконец, называть Release()
  7. если ReadDirectoryChangesW из DoRead() неудачу (как результат не будет, обратный вызов) - нужен прямой обратный вызов вызова с кодом ошибки
  8. для остановки мы можем просто закрыть каталог ручку - в результате мы получили STATUS_NOTIFY_CLEANUP в обратном вызове

=========================== =======

//#define _USE_NT_VERSION_ 

class SPYDATA : 
#ifdef _USE_NT_VERSION_ 
    IO_STATUS_BLOCK 
#else 
    OVERLAPPED 
#endif 
{ 
    HANDLE _hFile; 
    LONG _dwRef; 
    union { 
     FILE_NOTIFY_INFORMATION _fni; 
     UCHAR _buf[PAGE_SIZE]; 
    }; 

    void DumpDirectoryChanges() 
    { 
     union { 
      PVOID buf; 
      PBYTE pb; 
      PFILE_NOTIFY_INFORMATION pfni; 
     }; 

     buf = _buf; 

     for (;;) 
     { 
      DbgPrint("%x <%.*S>\n", pfni->Action, pfni->FileNameLength >> 1, pfni->FileName); 

      ULONG NextEntryOffset = pfni->NextEntryOffset; 

      if (!NextEntryOffset) 
      { 
       break; 
      } 

      pb += NextEntryOffset; 
     } 
    } 

#ifdef _USE_NT_VERSION_ 
    static VOID WINAPI _OvCompRoutine(
     _In_ NTSTATUS dwErrorCode, 
     _In_ ULONG_PTR dwNumberOfBytesTransfered, 
     _Inout_ PIO_STATUS_BLOCK Iosb 
     ) 
    { 
     static_cast<SPYDATA*>(Iosb)->OvCompRoutine(dwErrorCode, (ULONG)dwNumberOfBytesTransfered); 
    } 
#else 
    static VOID WINAPI _OvCompRoutine(
     _In_ DWORD dwErrorCode, // really this is NTSTATUS 
     _In_ DWORD dwNumberOfBytesTransfered, 
     _Inout_ LPOVERLAPPED lpOverlapped 
     ) 
    { 
     static_cast<SPYDATA*>(lpOverlapped)->OvCompRoutine(dwErrorCode, dwNumberOfBytesTransfered); 
    } 
#endif 

    VOID OvCompRoutine(NTSTATUS status, DWORD dwNumberOfBytesTransfered) 
    { 
     DbgPrint("[%x,%x]\n", status, dwNumberOfBytesTransfered); 

     if (0 <= status) 
     { 
      if (status != STATUS_NOTIFY_CLEANUP) 
      { 
       if (dwNumberOfBytesTransfered) DumpDirectoryChanges(); 
       process_list_of_existing_files();// so hard do this here ?!? 
       DoRead(); 
      } 
      else 
      { 
       DbgPrint("\n---- NOTIFY_CLEANUP -----\n"); 
      } 
     } 

     Release(); 
     MyReleaseRundownProtection(); 
    } 

    ~SPYDATA() 
    { 
     Cancel(); 
    } 

public: 

    void DoRead() 
    { 
     if (MyAcquireRundownProtection()) 
     { 
      AddRef(); 
#ifdef _USE_NT_VERSION_ 
      NTSTATUS status = ZwNotifyChangeDirectoryFile(_hFile, 0, 0, this, this, &_fni, sizeof(_buf), FILE_NOTIFY_VALID_MASK, TRUE); 
      if (NT_ERROR(status)) 
      { 
       OvCompRoutine(status, 0); 
      } 
#else 
      if (!ReadDirectoryChangesW(_hFile, _buf, sizeof(_buf), TRUE, FILE_NOTIFY_VALID_MASK, (PDWORD)&InternalHigh, this, 0)) 
      { 
       OvCompRoutine(RtlGetLastNtStatus(), 0); 
      } 
#endif 
     } 
    } 

    SPYDATA() 
    { 
     _hFile = 0;// ! not INVALID_HANDLE_VALUE because use ntapi for open file 
     _dwRef = 1; 
#ifndef _USE_NT_VERSION_ 
     RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED)); 
#endif 
    } 

    void AddRef() 
    { 
     InterlockedIncrement(&_dwRef); 
    } 

    void Release() 
    { 
     if (!InterlockedDecrement(&_dwRef)) 
     { 
      delete this; 
     } 
    } 

    BOOL Create(POBJECT_ATTRIBUTES poa) 
    { 
     IO_STATUS_BLOCK iosb; 
     NTSTATUS status = ZwOpenFile(&_hFile, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_DIRECTORY_FILE); 
     if (0 <= status) 
     { 
      return 
#ifdef _USE_NT_VERSION_ 
       0 <= RtlSetIoCompletionCallback(_hFile, _OvCompRoutine, 0); 
#else 
       BindIoCompletionCallback(_hFile, _OvCompRoutine, 0); 
#endif 
     } 
     return FALSE; 
    } 

    void Cancel() 
    { 
     if (HANDLE hFile = InterlockedExchangePointer(&_hFile, 0)) 
     { 
      NtClose(hFile); 
     } 
    } 
}; 

void DemoF() 
{ 
    if (MyInitializeRundownProtection()) 
    { 
     STATIC_OBJECT_ATTRIBUTES(oa, "<SOME_DIRECTORY>"); 

     if (SPYDATA* p = new SPYDATA) 
     { 
      if (p->Create(&oa)) 
      { 
       p->DoRead(); 
      } 

      //++ GUI thread run 
      MessageBoxW(0, L"wait close program...", L"", MB_OK); 
      //-- GUI thread end 

      p->Cancel(); 

      p->Release(); 
     } 

     MyWaitForRundownProtectionRelease(); 
    } 
} 
+0

Я не вижу, как IOCP/ACP соответствует моей задаче. Если вы посмотрите на фрагмент кода из ВВЕДЕНИЯ, вся путаница вращается вокруг этого: мне нужно знать * сразу после обработки списка существующих файлов *, если новые файлы были добавлены в каталог. Я не понимаю, как IOCP/ACP скажет мне * в этот момент *, если в каталоге есть новые файлы. Не могли бы вы помочь мне разобраться? Спасибо – AlwaysLearningNewStuff

+0

@AlwaysLearningNewStuff - сделайте свою задачу (обработайте список существующих файлов) - в обратном вызове. в чем проблема? – RbMm

+0

@AlwaysLearningNewStuff - снова и снова - вы совершенно не понимаете асинхронное программирование вообще и этот случай. только это не подходит для вашей задачи. – RbMm