Почему функция WaitForMultipleObjects возвращает ERROR_INVALID_PARAMETER, когда все параметры кажутся мне корректными?

Клиент попросил помощи с функцией WaitForMultipleObjects:

Я получаю ошибку ERROR_INVALID_PARAMETER, когда вызываю функцию WaitForMultipleObjects, даже несмотря на то, что все параметры, насколько я вижу, корректны. Я проиллюстрирую проблему на примере вот такой простой программы.

int main() {   int i;   HANDLE Handles[4];   // Создаем события   for (i = 0; i < 4; i++) {     Handles[i] = CreateEvent(NULL, FALSE, FALSE, TEXT("Test"));     if (Handles[i] == NULL) {        printf("Не удалось создать событие - тест провален\n"); return 0;     }   }   // Устанавливаем их все в сигнальное состояние   for (i = 0; i < 4; i++) SetEvent(Handles[i]);   // Ждем все события — мы ожидаем, что эта операция вернет WAIT_OBJECT_0   printf("WaitForMultipleObjects вернула %d\n",          WaitForMultipleObjects(4, Handles, TRUE, INFINITE));   return 0; }

Во-первых, большое спасибо за то, что вы свели проблему к минимальной демонстрационной программе. Вы, наверняка, удивитесь, если узнаете, как часто клиенты говорят: «У меня проблема с функцией X. Вот программа, которая иллюстрирует эту проблему». И прикрепляют огромный проект, который даже не компилируется, потому что он написан в другом системном окружении, отличном от того окружения, которое используется на твоем компьютере.

Проблема заключается в том, что вы передаете четыре дескриптора одного и того же события в функцию WaitForMultipleObjects с параметром bWaitAll установленном в TRUE. Функция WaitForMultipleObjects отвергает дубликаты, если вы просите ожидать ее все переданные объекты. Как же так получилось?

Что ж, давайте, рассмотрим эту программу: она создает именованное автоматически сбрасываемое событие (auto-reset event) (что «очевидно» из второго параметра функции CreateEvent, установленного в FALSE) и сохраняет его в элементе Handles[0]. Второй, третий и четвертый вызовы CreateEvent просто создают новые дескрипторы на то же самое автоматически сбрасываемое событие, поскольку переданное наименование соответствует наименованию существующего события. Второй цикл устанавливает это событие в сигнальное состояние четыре раза подряд. А затем мы просим функцию WaitForMultipleObjects ожидать, когда все объекты, на которые ссылаются переданные дескрипторы, перейдут в сигнальное состояние. Но поскольку все дескрипторы ссылаются на один и тот же объект, мы, фактически, просим функцию ожидать, когда наше событие перейдет в такое состояние, когда сигнальное состояние будет установлено одновременно сразу четыре раза. (Ха?)

Вспомните, что функция WaitForMultipleObjects не изменяет состояния ожидаемых объектов, пока ожидание не будет завершено. Если вы просите ее одновременно ожидать событие и какой-либо семафор (semaphore), и при этом событие перешло в сигнальное состояние, а семафор — еще нет, функция оставляет событие в сигнальном состоянии, пока производится ожидание семафора. Только после того, как все ожидаемые объекты перейдут в сигнальное состояние, функция WaitForMultipleObjects выполнит все необходимые действия для получения информации об объекте, который перешел в сигнальное состояние, и вернет управление.

Итак, мы попросили ее ожидать одно и то же автоматически сбрасываемое событие четыре раза. Но вот в чем незадача: автоматически сбрасываемое событие — это простой безмозглый семафор, который может иметь не более одного токена. Но для того, чтобы ожидание закончилось успешно, ему необходимо четыре токена. Этого никогда не произойдет, следовательно, такое ожидание бессмысленно.

В сущности, WaitForMultipleObjects возвращает ERROR_INVALID_PARAMETER, если вы передаете bWaitAll = TRUE и в массиве дескрипторов присутствуют дубликаты (либо одинаковые дескрипторы, либо разные дескрипторы, ссылающиеся на один и тот же объект). Она не пытается сложить головоломку из переданных объектов и не говорит: «Хм, посмотрим, есть ли здесь какая-нибудь разумная комбинация объектов, которые можно ожидать более одного раза», она просто видит дубликаты и говорит: «Забудь об этом!»

Возвращаясь к изначальной проблеме клиента: мы спросили, почему они создают четыре дескриптора на один и тот же объект, и что они хотели получить, предполагая, что у автоматически сбрасываемого события будет четыре доступных токена (чего быть не может), и клиент признал свою ошибку в коде. Изначальная версия кода использовала одно именованное событие и ожидала его при помощи функции WaitForSingleObject, а затем они модифицировали код для поддержки множества событий, но при этом забыли дать событиям различающиеся имена.