MyOldAccount Ответов: 1

Асинхронный вызов 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

1 Ответов

Рейтинг:
10

Jochen Arndt

Я еще не пользовался ReadDirectoryChanges до сих пор, но этого должно быть достаточно, чтобы вызвать его только один раз вне цикла. Это должно решить проблему.

Если нет, то вам придется ждать обоих событий, используя один вызов. Но я все равно рекомендую это сделать:

HANDLE ahWait[2] = { p.hEvent, ovl.hEvent };
BOOL bStop = FALSE;
// Start overlapped IO operation here
while (!bStop)
{
    switch (::WaitForMultipleObjects(2, ahWait, FALSE, INFINITE))
    {
    case WAIT_OBJECT_0 :
        bStop = TRUE;
        break;
    case WAIT_OBJECT_0 + 1 :
        // Process overlapped result here
        //::GetOverlappedResult(p.hDir, &ovl, &bytesTransferred, TRUE);
        // Restart overlapped IO operation here if necessary
        break;
//    case WAIT_TIMEOUT :
//        break;
    case WAIT_FAILED :
        // Handle wait error
        break;
    }
}


MyOldAccount

Спасибо, 5эд.