MyOldAccount Ответов: 1

Использование 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)
    {
        foo();

        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 something, process it
        if(error) processBuffer(buffer);
        // continue with the loop so foo() can be executed
        else if(error == ERROR_IO_PENDING) continue;
        // RDCW error
        else return ::GetLastError(); 
    }
}


ПРОБЛЕМА:

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

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

ВОПРОС:

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

Опять же, смысл состоит в том, чтобы использовать ReadDirectoryChangesW асинхронно, в цикле, как показано во фрагменте из введения.

Что я уже пробовал:

Я пробовал использовать 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 != ::GetLastError())
            {
                ::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.

1 Ответов

Рейтинг:
2

Jochen Arndt

Вы должны позвонить GetOverlappedResult() потому что это обработка завершения ReadDirectoryChanges() вызов. Если не делать этого и не звонить ReadDirectoryChanges() опять же, вы используете системные ресурсы до тех пор, пока они больше не будут доступны, как указано в коде ошибки.

Последний параметр здесь не должен иметь значения, потому что событие запускается, когда операция завершена (больше не ожидается). Проходящий FALSE это необходимо только тогда, когда операция ввода-вывода может возвращать частичные данные, а ваш код может обрабатывать частичные данные. Но с ReadDirectoryChanges() этого не должно случиться.

Общая процедура использования перекрывающегося ввода-вывода такова:


  • Вызовите функцию операции ввода-вывода, когда ни один предыдущий вызов не находится в ожидании
  • Обработайте его, когда возвращаемое значение истинно
  • Когда возвращаемое значение равно false и операция находится в ожидании:

    • Ждите события
    • Когда событие произошло, позвоните GetOverlappedResult() Данные присутствуют в буфере, переданном в операцию ввода-вывода. При необходимости сбросьте это событие. Теперь можно снова вызвать операцию ввода-вывода.
    • Обрабатывайте другие события, такие как событие для завершения потока, ошибки и тайм-ауты по мере необходимости
  • Если возвращаемое значение равно false и произошла ошибка (не ожидающая), обработайте ее по мере необходимости


MyOldAccount

Моя проблема в том, что я не могу дождаться завершения ввода-вывода (ожидая события бесконечно).

Если IO находится в ожидании, мой цикл должен продолжить выполнение и определить "на лету", когда IO завершится (и обработать Результат после этого).

Jochen Arndt

IO-это в случае изменения каталога. Пока нет изменения каталога (нет ввода-вывода), вызов находится в ожидании, и вы можете ждать. Как только происходит изменение каталога, событие запускается, возвращается вызов wait, и GetOverlappedResult() заполняет буфер.

MyOldAccount

Пока нет изменения каталога (нет ввода-вывода), вызов находится в ожидании, и вы мочь ждать.

Я все время говорю тебе, что могу нет подождите, пока ИО закончит. Если нет данных, я должен продолжить цикл. Вот это-то меня и смущает.

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

Надеюсь, я прояснил проблему.

Jochen Arndt

Нет, это не уточняется.

Почему вы не можете ждать (что нужно делать)?

Если это что-то не связанное с ReadDirectoryChanges(), сделайте это в другом потоке. Если это связано, то чего вы ожидаете?

Хотя никакие файлы в каталоге не изменяются каким-либо образом, ничего не происходит, и поток ждет. Как только файл изменяется, событие срабатывает, состояние ожидания завершается, и вы можете обработать это событие. Как только это будет обработано, вызовите ReadDirectoryChanges() и снова подождите.

Иначе говоря:
Цикл продолжается каждый раз, когда происходит изменение файла.

Поэтому я не вижу вашей проблемы (кроме того, что вы делаете все внутри своей функции main() вместо использования рабочего потока).

MyOldAccount

Нет, это не уточняется.Почему вы не можете ждать (что нужно делать)?

Прошу прощения за путаницу. Чтобы объяснить это, пожалуйста, посмотрите на фрагмент кода из введения.

У меня есть действие, которое нужно выполнять всегда, foo(); во фрагменте.

Если ReadDirectoryChangesW() заполняет буфер, затем я выполняю processBuffer(...) в противном случае цикл должен продолжаться.

Ваше решение блокирует цикл до тех пор, пока ввод-вывод не будет завершен, и в этом заключается проблема.

Когда нет данных или IO еще не закончил, я не могу ждать. Я должен продолжить цикл, выполняя foo().

Во время цикла я должен каким-то образом определить, что IO закончен, а затем processBuffer(...)... Вот где GetOverlappedResult(...) вступать в игру.

Надеюсь, теперь все ясно, иначе оставьте комментарий.

Спасибо снова.

Jochen Arndt

Ок, что делает его понятным.
Но я уже ответил на этот вопрос:

"Если это что-то не связанное с ReadDirectoryChanges(), сделайте это в другом потоке."

Или поместите обработку ReadDirectoryChanges () в свой собственный поток.

Однако если foo () также ожидает определенных событий, вы все равно можете использовать один поток при обработке нескольких событий с помощью WaitForMultipleObjects.

В целом это вопрос проектирования ваших задач.

Самым простым решением здесь был бы опрос (то есть проверка вручную, произошло ли событие, и обработка его затем без ожидания). Если это так, вы не должны снова вызывать ReadDirectoryChanges (), пока он находится в ожидании, и вызывать GetOverlappedResult (), когда произошло событие.

Но это было бы плохим решением, потому что оно производит тяжелую системную нагрузку, когда foo() не ждет (блокирует одно ядро процессора).

MyOldAccount

Мне удалось решить эту проблему благодаря ответ от переполнения стека. Принятое решение иллюстрирует то, что я пытался сделать.

Спасибо за вашу помощь, с наилучшими пожеланиями до следующего раза.

Jochen Arndt

Хорошо. Спасибо за ваши отзывы.

Но использование WaitForSingleObject () всегда с нулевым таймаутом бесполезно. Вместо этого вы можете использовать состояние события или GetOverlappedResult (), чтобы проверить, произошло ли изменение каталога.

Как уже упоминалось, он создает большую нагрузку на систему.
Проверяли ли вы загрузку процессора вашей программы (например, с помощью Диспетчера задач)?

MyOldAccount

Но использование WaitForSingleObject () всегда с нулевым таймаутом бесполезно. Вместо этого вы можете использовать состояние события или GetOverlappedResult (), чтобы проверить, произошло ли изменение каталога.

Я больше не использую WaitForSingleObject, мое решение применяет отредактированную часть принятого ответа (посмотрите в нижней части ответа).

Извините за поздний ответ, я был занят.