Часть 1 Часть 2 Часть 2.5 Часть 2.6 Часть 3 Во всех предыдущих статьях мы рассматривали лишь самый простой пример — последовательный вывод сообщений на экран в отдельном потоке. Пришло время, наконец, сделать что-то более реальное и существенное, пусть и не очень сложное. И этим будет менеджер http get запросов.  Предположим, что мы хотим сначала выполнить некоторую задачу (возьмём вывод "Hello, world"), а после того, как эту задачу выполним, загрузить из сети некоторые данные (возмём страницу документации Qt), и вывести размер загруженных данных в байтах (не будем выводить сами данные, чтобы не загромождать вывод). Первой попыткой может стать следующий код Сначала создаём shared_pointer на менеджер запросов и active object принтера, затем выводим "Hello, world!", после чего пытаемся сделать get-запрос из менеджера, и в конце приложения выводим результат его работы (success, cancel или fail). Не забываем и про то, что когда мы создаём QObject в многопоточной среде, мы не должны ни в коем случае дёргать delete. Вместо этого нужно использовать метод deleteLater. Поэтому в строке 8 при создании shared_ptr вторым параметром передаётся стандартный Qt-шный deleter, который вызывает deleteLater. Объекты printer и httpManager живут на протяжении всего времени работы приложения, поэтому выдавать deleter им нет смысла. Результат запуска нас огорчит. Мы не можем сделать вызов метода get из потока, который не владеет менеджером. Это связано с тем, что метод get создаст объект QNetworkRequest, являющийся наследником httpManager. Qt не рекомендует добавление QObject в дерево объектов, не принадлежащих потоку. Это может работать, но будет undefined behaviour. Тогда мы попробуем создать и httpManager, и запрос в continuation. Ведь тогда они будут в одном потоке, и жизнь будет легка и хороша. Не забываем добавить httpManager в список захвата лямбды, чтобы он не был удалён раньше времени. Но и теперь у нас ничего не выйдет. На этот раз всё сломалось из-за флага QtFuture::Launch::Async во второй строке. Он заставил continuation (лямбду) выполняться на одном из потоков QThreadPool::globalInstance(). Этот поток обёрнут в QThreadPoolThread. А этот класс не предполагает запуск цикла событий. А значит, любой QObject, принадлежащий этому потоку, будет мёртв и не сможет обмениваться событиями, сигналами и слотами. Хорошо. Тогда мы отключим исполнение на QThreadPool и заставим continuation выполняться силами потока-владельца active object.   И да. Это работает. Но мы имеем крайне мерзкую нежизнеспособную систему, в которой на каждый запрос придётся делать свой QNetworkAccessManager. Второй пример точно также не работал из-за QtFuture::Launch::Async. Если убрать его, то картина будет следующей  Проблема в том, что мы всё равно создаём QObject в методе get из потока, который не владеет httpManager. Конкретно здесь, на моей машине, в моей версии Qt, в этом коде это не выстрелило. Но это undefined behaviour, и делать так нельзя. Итого, имеем, что без танцев с бубной мы можем либо не параллелить вовсе, либо создавать свой менеджер на каждый запрос, что грустно. Именно поэтому мы создадим Active Object, занимающийся обработкой http-запросов. Он будет основан на идее EventBasedAsyncQDebugPrinter из предыдущей статьи. HttpGetRequestEvent является просто dto для передачи запроса между потоками. В метод get передаётся объект QNetworkRequest, содержащий все данные для совершения get-запроса, создаётся HttpGetRequestEvent, берётся future от него, событие-задача отправляется самому себе через postEvent(), после чего вызывающей стороне возвращается future на результат запроса.  При этом, когда результат оборачивается в shared_ptr, желательно добавить QScopedPointerDeleteLater{}, как того просит документация Qt.  В методе custonEvent происходит отлов события и выполнение запроса. Когда запрос будет выполнен, его результат будет записан в promise. Использование класса крайне сильно похоже на использование QNetworkAccessManager.  Но при этом запрос может быть выполнен из абсолютно любого потока, continuation может быть также обработан абсолютно любым потоком. Использовать QtFuture::Launch::Async тоже можно, что даёт достаточно большой запас гибкости. А что если мы захотим, помимо get, делать post/put или любые другие запросы? Без проблем. Нужно лишь расширить HttpManager. В качестве примера, добавим поддержку post-запросов. Для начала создадим dto для передачи post-запроса между потоками Класс практически повторяет HttpGetRequestEvent, но дополнительно переносит QByteArray с параметрами. Теперь добавим обработку этого события в метод customEvent Сначала достаём из события promise и создаём запрос в зависимости от типа события, а затем делаем QtFuture::connect, в котором заполним promise. Ну и последним шагом останется добавить в public-секцию HttpManager метод post, принимающий параметры запроса, и возвращающий QFuture на shared_ptr<QNetworkReply>. Код практически повторяет код метода get. Меняется лишь создаваемое событие. Попробуем использовать новый метод. В качестве теста кинет post-запрос на страницу Хабра со второй частью цикла.  Хабр не позволяет post-запросы к постам, поэтому получим ошибку. Из этой ошибки мы видим, что post запрос всё-таки был проведён. А больше нам и не надо. Подобным образом можно расширить HttpManager любыми нужными вам методами. Эта статья вышла самой длинной в цикле, в ней была куча кода, как рабочего, так и не очень. Код, как обычно, на GitHub.  Если есть желание увидеть реализацию ещё каких-то active object — пишите в личку.