Асинхронный вызов readdirectorychangesw блокирует выход потока
ВСТУПЛЕНИЕ:
Я пишу небольшое приложение, которое отслеживает определенный каталог для новых добавленных файлов.
Я хотел бы поместить код мониторинга в отдельный поток, чтобы я мог оставить основной поток свободным для других вещей и отменить поток мониторинга, когда мне это нужно.
СООТВЕТСТВУЮЩАЯ ИНФОРМАЦИЯ:
- Я использую ReadDirectoryChangesW[^] чтобы сделать мониторинг
- Я использую raw
WIN32 API
для создания/синхронизации потоков - Я пытаюсь поддерживать Windows XP и дальше;
ПРОБЛЕМА:
Я смог правильно закодировать все, кроме одной вещи:
Я не могу выйти из потока мониторинга должным образом, отсюда и этот пост.
Я сигнализирую объекту события в основном потоке, жду выхода потока и затем выполняю очистку.
Проблема заключается в моем использовании
ReadDirectoryChangesW
поскольку все работает нормально после того, как я закомментирую этот фрагмент кода.Как только дескриптор события будет сигнализирован,
ReadDirectoryChangesW
блокирует поток, который мешает ему "поймать" событие и выйти. Если я добавлю новый файл в каталог, он "разблокируется" ReadDirectoryChangesW
, поток "ловит" событие и выходит.Чтобы помочь дальше, я сделал небольшой MVCE ниже, который иллюстрирует то, что я сказал до сих пор.
MVCE:
#include <iostream> #include <windows.h> #include struct SThreadParams { HANDLE hEvent; HANDLE hDir; int processDirectoryChanges(const char *buffer) { if (NULL == buffer) return -1; 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 << "FILE_ACTION_ADDED " << fileName << std::endl; } break; case FILE_ACTION_REMOVED: { std::cout << "FILE_ACTION_REMOVED " << fileName << std::endl; } break; case FILE_ACTION_MODIFIED: { std::cout << "FILE_ACTION_MODIFIED " << fileName << std::endl; } break; case FILE_ACTION_RENAMED_OLD_NAME: { std::cout << "FILE_ACTION_RENAMED_OLD_NAME " << fileName << std::endl; } break; case FILE_ACTION_RENAMED_NEW_NAME: { std::cout << "FILE_ACTION_RENAMED_NEW_NAME " << fileName << std::endl; } break; default: break; } // clear string so we can reuse it ::memset(fileName, '\0', sizeof(fileName)); // advance to next entry offset += fni->NextEntryOffset; } while (fni->NextEntryOffset != 0); return 0; } }; DWORD WINAPI thread(LPVOID arg) { SThreadParams p = *((SThreadParams *)arg); OVERLAPPED ovl = { 0 }; DWORD bytesTransferred = 0, error = 0; char buffer[1024]; if (NULL == (ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL))) { std::cout << "CreateEvent error = " << ::GetLastError() << std::endl; return ::GetLastError(); }; do { if (::ReadDirectoryChangesW(p.hDir, buffer, sizeof(buffer), FALSE, FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ovl, NULL)) { if (::GetOverlappedResult(p.hDir, &ovl, &bytesTransferred, TRUE)) { for (int i = 0; i < 5; ++i) std::cout << '='; std::cout << std::endl; if (-1 == p.processDirectoryChanges(buffer)) std::cout << "processDirectoryChanges error = " << std::endl; } else { bytesTransferred = 0; std::cout << "GetOverlappedResult error = " << ::GetLastError() << std::endl; } if (0 == ::ResetEvent(ovl.hEvent)) { std::cout << "ResetEvent error = " << ::GetLastError() << std::endl; ::CloseHandle(ovl.hEvent); return ::GetLastError(); } } else { // we shall just output the error, and try again... std::cout << "ReadDirectoryChangesW error = " << ::GetLastError() << std::endl; } error = ::WaitForSingleObject(p.hEvent, 2000); } while (WAIT_TIMEOUT == error); ::CloseHandle(ovl.hEvent); return 0; } int main() { SThreadParams s; s.hDir = ::CreateFile(SOME_DIRECTORY, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (INVALID_HANDLE_VALUE == s.hDir) { std::cout << "CreateFile error = " << ::GetLastError() << std::endl; return 1; } s.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); if (NULL == s.hEvent) { std::cout << "CreateEvent error = " << ::GetLastError() << std::endl; ::CloseHandle(s.hDir); return 1; } HANDLE hThread = ::CreateThread(NULL, 0, thread, (LPVOID)&s, 0, NULL); if (NULL == hThread) { std::cout << "CreateThread error = " << ::GetLastError() << std::endl; ::CloseHandle(s.hDir); ::CloseHandle(s.hEvent); return 1; } std::cout << "press any key to close program..." << std::endl; std::cin.get(); if (0 == ::CancelIoEx(s.hDir, NULL)) { std::cout << "CancelIoEx error = " << ::GetLastError() << std::endl; ::CloseHandle(s.hDir); ::CloseHandle(s.hEvent); return 1; } if (0 == ::SetEvent(s.hEvent)) { std::cout << "SetEvent error = " << ::GetLastError() << std::endl; ::CloseHandle(s.hDir); ::CloseHandle(s.hEvent); return 1; } // wait for thread to exit DWORD error = ::WaitForSingleObject(hThread, INFINITE); std::cout << "Thread exited with error code = " << error << std::endl; ::CloseHandle(s.hEvent); ::CloseHandle(s.hDir); ::CloseHandle(hThread); return 0; }
Что я уже пробовал:
- Я переместил перекрывающуюся структуру из потока в структуру, которая была передана потоку. Затем я установил перекрытие.hEvent принудительно "разблокировать" ReadDirectoryChangesW. Это, кажется, работает, но пугает меня, потому что я думаю, что это небезопасно/подвержено ошибкам, так как оно недокументировано.
- Я пытался использовать процедуры завершения, но не добился успеха, так как я новичок во всем этом. Я смог получать уведомления, но содержимое буфера (заполненного ReadDirectoryChangesW) не было прочитано должным образом после первого прохода. Я все еще пытаюсь сделать эту работу самостоятельно, но мне бы не помешала помощь.
- Я мог бы использовать порт завершения ввода-вывода, но так как я буду контролировать только один каталог, я думаю, что это немного перебор. Если я ошибаюсь, пожалуйста, проинструктируйте меня, как использовать порт завершения ввода-вывода для моего случая, я бы с удовольствием их опробовал.
Rick York
Здесь есть статья под названием "FindFirstChangeNotification &Shell_NotifyIcon вместе"... снова " Дэвида Кроу, который показывает, как использовать функцию FindFirstChangeNotification для выполнения того, что вы пытаетесь сделать. Я недавно использовал эту технику, и она отлично сработала для меня.
Вот ссылка: http://www.codeproject.com/Articles/20826/FindFirstChangeNotification-Shell-NotifyIcon-toget