Fruitsekta.ru

Мир ПК
1 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Qnetworkaccessmanager передать в поток

Эффективное использование QNetworkAccessManager

На странице справки для QNetworkAccessManager в разделе Detailed Description приводится следующее замечание:

Note: QNetworkAccessManager queues the requests it receives. The number of requests executed in parallel is dependent on the protocol. Currently, for the HTTP protocol on desktop platforms, 6 requests are executed in parallel for one host/port combination.

Означает ли это, что в случае различия в комбинации хост/порт или отправке запросов, скажем, на два различных адреса, запросы будут исполнены последовательно?

Для QNetworkRequest определён такой интересный атрибут, как HttpPipeliningAllowedAttribute . Связан ли этот атрибут с механизмом параллельной отправки запросов, например, с условием соответствия единой комбинации хост/порт?

У QNetworkAccessManager имеется метод предварительного подключения к серверной стороне на указанном порту:

Как я понимаю, это может отчасти или даже полностью нивелировать время, затрачиваемое на резолвинг доменного имени и так называемый TCP handshake. Каково количество подобных подключений будет являться наиболее эффективным? Должно ли это число соответствовать количеству разных комбинаций хост/порт или оно должно соответствовать количеству каналов (6 штук), которые организует QNetworkAccessManager при подключении к одной комбинации хост/порт?

1 ответ 1

Означает ли это, что в случае различия в комбинации хост/порт или отправке запросов, скажем, на два различных адреса, запросы будут исполнены последовательно?

Вы хотели сказать «паралельно», а не последовательно?

Данное условие нужно понимать так — не более 6 запросов на одно серверное подключение (хост:порт). Да, апач/нджинкс может слушать одновременно на 2 портах, тогда как бы получается 12 запросов. Но с клиентской стороны это очень сложно узнать.

В случае различных хостов запросы делаются «в паралель» (лично проверял на нескольких проектах). В случае различных портов не проверял, но думаю, что будет также работать. Правда, когда хосты виртуальные (и сидят за одним айпи), то вроде оно все таки не превышает лимита в 6 запросов. Но здесь у меня слишком противоречивые результаты. Поэтому, нужно будет отдельно поисследовать это вопрос и разобраться — хост — это доменное имя или айпи.

Эта штука позволяет оправлять несколько запросов, не дожидаясь ответов сразу. Естественно, сервер должен поддерживать http/1.1. То есть, отправляется много запросов, а потом ожидаем ответы, но это все в пределах одного соединения. Запросы на различные хосты не могут использовать эту фичу. Особенный выиграш от этой опции наступает тогда, когда запросы мелкие, независимые и их много, а сеть и их обработка жутко медленная.

Как я понимаю, это может отчасти или даже полностью нивелировать время, затрачиваемое на резолвинг доменного имени и так называемый TCP handshake. ?

да, может. А может и нет:)

Каково количество подобных подключений будет являться наиболее эффективным?

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

А если нужно просто выполнить один-два запроса, то тут что делай преконнект, что не делай — разницы не будет. Потому что первый запрос все равно выполнит этот самый преконнект.

Как всегда, в подобных случаях помогают бенчмарки. Мне известен один случай, когда человек делал что то типа ботов-краулеров и его спросили, каким образом он настраивает конфиг на максимальную производительность. После долгих расспросов выяснилось, что он вначале запускает специальный тестовый скрпипт, который проганят программу при различных настройках и выбирает максимум. Да, этот скрипт иногда работал по несколько суток, но зато потом выжимал с серверов все соки.

Читать еще:  Как создать автоформу в access 2020

Загрузка файла с помощью метода post() QNetworkAccessManager

у меня возникли проблемы с приложением Qt; в частности, с классом QNetworkAccessManager. Я пытаюсь выполнить простую HTTP-загрузку двоичного файла с помощью метода post () QNetworkAccessManager. В документации говорится, что я могу дать указатель на QIODevice to post () и что класс передаст данные, найденные в QIODevice. Это говорит мне о том, что я должен иметь возможность дать post() указатель на QFile. Для пример:

то, что, кажется, происходит в системе Windows, где я разрабатываю это, заключается в том, что мое приложение Qt выталкивает данные из QFile, но затем не завершает запрос; кажется, он сидит там, ожидая появления дополнительных данных из файла. Запрос post не «закрыт», пока я вручную не убью приложение, и в этот момент весь файл отображается на моем сервере.

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

Я подтвердил с некоторым тестовым кодом, что read () операция QFile просто возвращает ноль после того, как вы прочитали до конца файла. Это кажется несовместимым с тем, как метод post() QNetworkAccessManager ожидает, что QIODevice будет вести себя. Мои вопросы:

  1. это какое-то ограничение с тем, как QFile работает под Windows?
  2. есть ли другой способ использовать QFile или QNetworkAccessManager для перемещения файла через post()?
  3. это не собираюсь работайте вообще, и мне нужно будет найти другой способ загрузить мой файл?

любые предложения или советы будут оценены.

обновление: получается, что у меня две разные проблемы: одна на стороне клиента и на стороне сервера. На стороне клиента я должен был убедиться, что мой объект QFile оставался в течение всей сетевой транзакции. Метод post () QNetworkAccessManager возвращается немедленно, но на самом деле не завершен немедленно. Вам нужно прикрепить слот к сигналу finished () QNetworkAccessManager, чтобы определить, когда сообщение фактически завершено. В моем случае было достаточно легко сохранить QFile более или менее постоянно, но я также прикрепил слот к готовому сигналу (), чтобы проверить ответы на ошибки с сервера.

я прикрепил сигнал к слоту, как это:

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

завершенный(QNetworkReply*) сигнал от QNetworkAccessManager запускает мой метод postFinished(QNetworkReply*). Когда это произойдет, это безопасно для меня, чтобы закрыть сжатый файл и удалить файл данных, представленных сжатый файл. Для целей отладки я также добавил несколько операторов printf (), чтобы подтвердить, что транзакция завершена:

С compressedFile не закрывается сразу и не выходит за рамки, QNetworkAccessManager может занять столько времени, сколько ему нравится, чтобы передать мой файл. В конце концов транзакция завершена, и вызывается мой метод postFinished ().

Читать еще:  Ms access преимущества

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

Особенности работы с потоками в QT

Введение

Меня побудило написать эту статью серия тестовых заданий, которые я проверял. Я был крайне удивлен, что почти все они содержали однотипные ошибки. Многопоточное программирование по праву считается одной из сложных вещей для понимания. Но помимо концепции надо очень хорошо понимать особенности инструмента, который используется. В QT взаимодействие потоков организовано не совсем очевидным на первый взгляд образом. Оно несколько отличается от той модели потоков, которая представлена в native API операционных систем. Но если разобраться, то в QT все логично.

Все примеры в этой статье содержат только минимум кода для демонстрации концепции. Для компиляции их надо доработать.

Потоки в QT представлены классом QThread. Есть несколько вариантов выполнить код в потоке. Простой способ, это перегрузить метод QThread::run(), который вызывается в контексте нового потока. Мы будем рассматривать именно этот случай в наших примерах.

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

Пример, того как выполнить простейший код в другом потоке:

Архитектура QThread похожа на классы из других библиотек, но это совпадение только внешнее. Дальше начинаются тонкости. В первую очередь это касается очереди сообщений. Поток может и не использовать очередь сообщений. В этом случае у потомка QThread невозможно будет использовать механизм signal/slot в полной мере. Поток будет способен выбрасывать сигналы, а вот слоты работать не будут.

Если требуется работа со слотами или использовать некоторые объекты внутри потока (например, QNetworkAccessManager), то поток обязан иметь очередь сообщений.

Итак, самое простое QT приложение имеет главный поток и очередь сообщений, которая работает в контексте этого потока:

Идем дальше. Все потомки QObject «принадлежат» какому-то потоку. В примере выше, мы создали объект myDialog в контексте главного потока. По умолчанию, QT считает, что объект принадлежит тому потоку, в котором он был создан. Если при создании указать объекту родителя, который принадлежит другому потоку, то и новый объект будет принадлежать другому потоку.

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

Прежде чем мы продолжим, хочу сказать несколько слов о слотах. Посмотрим в документацию:

Нас интересует последний параметр. Если мы оставим его по умолчанию, то это значит следующее:

  • если мы соединяем объекты, которые принадлежат одному потоку, то тип подключения аналогичен Qt::DirectConnection
  • если мы соединяем объекты, которые принадлежат разным потокам, то тип подключения аналогичен Qt::QueuedConnection

В первом случае вызов слота выполняется в обход очереди сообщений, а во втором — формируется событие, которое попадает в очередь сообщений другого объекта. Т.е. вызов слота происходит в контексте другого потока.

Можно указывать тип подключения напрямую. Мы можем использовать Qt::QueuedConnection даже для объектов в одном потоке, если по какой то причине надо вызвать слот через очередь сообщений.

Читать еще:  Режим мастера в access

Теперь я покажу все это на примерах, чтобы было понятней.

Вот мы дошли до первого сюрприза QT. Мы создаем объект thread в контексте главного потока, поэтому он и будет его владельцем. Это выглядит нелогично для объекта, который сам представляет поток, но об этом надо постоянно помнить. Для нас это означает, что когда пользователь нажмет кнопку на диалоге, то у потока вызовется слот receiveSignal() в контексте главного потока, а не в контексте рабочего потока thread!

Но мы то создали MyThread для того чтобы выполнять операции в контексте другого потока, а то что мы видим выше совсем не то чего мы хотели. Т.е. получается, что поток крутит свой цикл очереди сообщений вхолостую.

Мы добавили в конструктор вызов moveToThread(this). Выглядит странно, не так ли? На самом деле мы меняем поток, который владеет объектом. Т.е. теперь объект thread начнет использовать свою очередь сообщений.

Теперь если пользователь нажмет на кнопку на форме — информация о сигнале попадет в очередь сообщений рабочего потока MyThread, а затем будет вызван слот receiveSignal() в его же контексте. Бинго! Это то, что нам и требовалось.

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

Давайте посмотрим, то происходит. Мы создаем объект типа QNetworkAccessManager (далее NAM) в конструкторе. Т.к. MyThread изначально принадлежит главному потоку приложения, то и новый объект наследует это свойство.

Дальше мы инициируем загрузку страницы в методе run(). По завершению операции NAM выбросит сигнал finished и будет вызван слот finishRequest. QNetworkAccessManager использует очередь сообщений для выполнения сетевых операций. А так как он принадлежит главному потоку, то он будет использовать очередь сообщений главного потока. Иными словами все сетевые операции будут выполняться в главном потоке, а не в рабочем как можно было ожидать.

Более того, слот finishRequest тоже вызовется в главном потоке. Т.е. если мы будем тут выполнять «тяжелую» операцию, мы затормозим пользовательский интефейс. Вызов в конструкторе moveToThread(this) — не решит проблему, потому что m_pNAM по прежнему создается в контексте главного потока. Надо еще добавить m_pNAM->moveToThread(this). Это заставит NAM использовать очередь сообщений нашего потока.

Т.е. правильный вариант будет:

Совет: чтобы детально разобраться, как это работает можно ставить точки останова внутри слотов. После этого достаточно в отладчике посмотреть контекст текущего потока и стек вызова, чтобы понять, как произошел вызов слота.

А теперь вариация на ту же тему, но пример более интересный:

Теперь мы создаем m_pNAM в методе run(). Т.е. объект у нас создается в контексте нашего потока, а это значит, что он будет использовать очередь сообщений потока MyThread. Но обратите внимание, что сам класс MyThread по прежнему принадлежит другому потоку.

Поэтому NAM будет выполнять все свои внутренние операции в контексте рабочего потока, но при этом сигнал QNetworkAccessManager::finished(QNetworkReply*) вызовет слот finishRequest(QNetworkReply*) в контексте главного потока. Иногда это может быть полезно, но чаще нет.

Если у вас остались вопросы — спрашивайте.

Copyright (C) Kudinov Alexander, 2006-2010

Перепечатка и использование материалов запрещена без писменного разрешения автора.

Ссылка на основную публикацию
Adblock
detector
×
×