Диалог запуска в основном потоке, ожидающий результата рабочего потока

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

Я использую C ++ REST SDK для отправки HTTP-запросов. Это происходит в другом потоке (pplx :: task). Я также использую модальное диалоговое окно MFC для запроса аутентификации. Некоторые из вас могут увидеть возникшую тупиковую ситуацию. Если нет, позвольте мне объяснить подробнее.

Основной поток ожидает завершения HTTP-запроса. Внутри этого потока я ловлю 401 и хочу запустить диалог. Для этого я использую boost::signal. Этот сигнал вызывает SendMessage дескриптора, который я хочу отобразить. После того, как сообщение будет обработано циклом сообщений MFC, он запустит диалог (в основном потоке). Это зависит от цикла сообщений MFC, который заблокирован в ожидании HTTP-запроса. Короче говоря, основной поток уже ожидает завершения запроса, поэтому он не может запустить цикл сообщений для получения вызова от SendMessage.

Основной поток ожидает рабочего потока. Рабочий поток должен запустить диалог в основном потоке, прежде чем он сможет продолжить. Тупик. Есть ли у кого-нибудь умные решения для решения этой проблемы?


person anon    schedule 20.03.2020    source источник
comment
Вы определенно хотите использовать потоки, но вы должны позволить потоку пользовательского интерфейса «дышать». Если что, отключите все элементы управления, кроме кнопки «Отмена». Не ждите, пока эти потоки завершатся на уровне потоков. Вместо этого попросите их уведомить диалоговое окно о своем результате с помощью вызова метода (или, что еще лучше, вызвать метод состояния обновления в диалоговом окне, чтобы получить большее разрешение для состояния каждого потока или процента завершения или ???).   -  person franji1    schedule 20.03.2020
comment
Ваше описание немного сбивает с толку. Сколько у вас диалогов? Только один (для аутентификации) или больше? Кроме того, является ли отображение диалогового окна аутентификации первым действием, которое нужно предпринять, или это тоже должно ждать завершения некоторого HTTP-запроса? Пожалуйста, опишите более подробно, а главное в правильном порядке, последовательность желаемых действий и событий.   -  person Constantine Georgiou    schedule 20.03.2020
comment
Для простоты скажем, что есть основной диалог и диалог авторизации. При взаимодействии с основным диалоговым окном запускаются HTTP-запросы. Меня больше всего беспокоит истечение срока аутентификации и попытки отправить запрос. На практике это случается редко. Если это так, я хочу открыть диалоговое окно аутентификации и повторить запрос. Вот как выглядит последовательность действий: пользователь нажимает кнопку btn в главном диалоговом окне. Btn запускает HTTP. Если 200, отобразить результат ответа. Если 401, отобразить диалоговое окно авторизации. В случае успеха повторите попытку HTTP. Если ответом является еще один сбой или проверка подлинности не выполняется, допустим, программа закрывается.   -  person anon    schedule 20.03.2020
comment
Итак, ваше первое действие - отобразить основной диалог. Вопрос в том, разрешено ли пользователю что-либо делать в основном диалоговом окне, если аутентификация не была выполнена или нет? В противном случае может быть лучшим дизайнерским решением сначала выполнить аутентификацию и отображать главное диалоговое окно только в случае успеха.   -  person Constantine Georgiou    schedule 20.03.2020
comment
Пользователь может выполнять ограниченные действия без аутентификации. В большинстве случаев пользователь автоматически входит в систему или получает запрос при первом запуске. Так что обычно это не проблема. Однако, если приложение открыто в течение длительного периода времени (и не обновляло токены), срок аутентификации может истечь, что означает, что их запрос завершится ошибкой. На этом этапе им придется вручную запрашивать вход в систему. И если произойдет сбой в середине серии запросов, это может быть плохо, если данные останутся в недопустимом состоянии. В идеале я бы дождался, пока они снова войдут в систему, повторно отправят неудавшийся запрос и продолжат свой путь.   -  person anon    schedule 20.03.2020
comment
Затем следует немного переработать основной диалог. Некоторые элементы пользовательского интерфейса (меню, элементы управления и т. Д.) Должны быть изначально отключены и включены только в случае успешной аутентификации. То есть некоторые операции должны быть включены в зависимости от статуса аутентификации, который необходимо отслеживать. Я прав? Еще лучше, установите некоторый контроль, указывающий статус аутентификации (например, Нет соединения, Соединяется ..., Соединен и т. Д.). Все операции, связанные с пользовательским интерфейсом, лучше всего выполнять в контексте основного потока, а все операции, связанные с HHTP, - в рабочих потоках. Основной поток никогда не должен блокироваться.   -  person Constantine Georgiou    schedule 20.03.2020
comment
@con: Это вам ничего не дает. Вам придется снова отключить эти элементы пользовательского интерфейса, когда срок действия токенов истечет. И единственный способ узнать, что срок действия токенов истек, - это наблюдать за HTTP-ответом. К тому времени, когда вы обнаружите, что пользовательский интерфейс должен быть обновлен, уже слишком поздно препятствовать взаимодействию пользователя с пользовательским интерфейсом, который будет запускать HTTP-запросы, которые завершатся ошибкой.   -  person IInspectable    schedule 20.03.2020
comment
Даже если я отключу кнопки до тех пор, пока у нас не будет аутентификации, это не решит проблему. Скажите, что пользователь успешно вошел в систему. Теперь кнопки включены. auth истекает. Пользователь нажимает кнопку. 401. В точности то, что сказал @IInspectable.   -  person anon    schedule 20.03.2020
comment
@anon и @IInspectable, рабочие потоки, конечно, должны уведомлять основной поток о любых изменениях, касающихся статуса аутентификации, как только это происходит (например, путем публикации настраиваемого сообщения, такого как WM_APP + nnn), и основной поток должен соответствующим образом обновлять пользовательский интерфейс. . Что-то не так с этим дизайном? Есть ли лучшая альтернатива?   -  person Constantine Georgiou    schedule 20.03.2020
comment
Вот что происходит, но основной поток не может ответить на настраиваемое сообщение, потому что он ожидает результата рабочего потока. К сожалению, приложение довольно велико и потребовало бы много переписывания, если бы я отправлял HTTP-ответы с настраиваемым сообщением о том, что они готовы обновить пользовательский интерфейс. Запросы / ответы обычно небольшие и происходят быстро, поэтому в основном диалоговом окне это не заметно. Но, конечно, становится очевидным, когда он заходит в тупик.   -  person anon    schedule 20.03.2020
comment
Я не очень знаком с PPL. Я считаю, что вы можете связать продолжения и обрабатывать оттуда обновление токенов, не блокируя пользовательский интерфейс. Если вы убедитесь, что секция кода, обрабатывающая обновление токена, защищена от повторного входа, все должно работать нормально.   -  person IInspectable    schedule 21.03.2020
comment
То, что основной поток ожидает завершения некоторого рабочего потока, по сути очень похоже на выполнение операции в основном потоке. Оба приводят к блокировке основного потока. Нет? Это вопрос спецификаций дизайна пользовательского интерфейса. Если это приемлемо или слишком нерентабельно пересматривать дизайн приложения, вам придется смириться с этим или исправить это. Например, если все, что вам нужно сделать, это отобразить диалоговое окно аутентификации, вы можете сделать это в недолговечном новом потоке (он создаст цикл сообщений), а лучше в WinAPI, а не в MFC.   -  person Constantine Georgiou    schedule 21.03.2020


Ответы (1)


Я думаю, что упрощенное решение здесь - изменить способ обработки потоков.

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

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

StatusCode make_reqeust(...) {
  // Deal with the logic on authentication here
}

Где StatusCode - это тип кода состояния HTTP.

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

person Object object    schedule 20.03.2020
comment
У каждого запроса есть свой поток. Я всегда могу проверить статус, прежде чем обрабатывать каждый ответ, но я надеялся обработать случай 401 внутренне, поэтому снаружи я мог просто беспокоиться о содержимом ответа. - person anon; 20.03.2020
comment
Внутренне? Как в функции? Или класс? - person Object object; 20.03.2020
comment
Внутри потока HTTP-запросов. В противном случае мне пришлось бы проверять и запрашивать авторизацию каждый раз, когда я обрабатываю запрос. В идеале я мог бы перехватить и обработать запрос (только для 401) перед возвратом ответа, который затем будет обработан для его содержимого. - person anon; 20.03.2020
comment
Конечно, поэтому поместите его в функцию, которая возвращает окончательный код после возможной повторной попытки аутентификации. - person Object object; 20.03.2020
comment
Я мог бы инкапсулировать это синхронно, но если выполняется асинхронный запрос (возвращает поток), код состояния неизвестен, пока он не присоединится. На этом этапе он готов к обработке своего контента и возлагает бремя запроса на аутентификацию извне. Если я попытаюсь выполнить какие-либо проверки в потоке, мы вернемся к исходной проблеме. - person anon; 20.03.2020
comment
Смотрите мою правку по этому поводу, вероятно, следовало бы объяснить, как обрабатывать логику в потоке пользовательского интерфейса: D - person Object object; 20.03.2020
comment
Я не уверен, полностью ли я понимаю, что, если я верну будущий ‹code›, не будет ли запрос авторизации по-прежнему появляться в потоке? Даже если что-то в основном потоке проходит через фьючерсы и запрашивает авторизацию там, как я могу повторно отправить запрос, чтобы «изменить» это будущее на 200 вместо 401? - person anon; 20.03.2020
comment
Std :: future - это просто пример. Его можно использовать как путь между двумя потоками, но также можно использовать такие вещи, как атомарные переменные. так что просто ждите, например, изменения в рабочем потоке, а затем действуйте с учетными данными, переданными через - person Object object; 20.03.2020