Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: DrWebber от Январь 15, 2015, 06:03



Название: Парсинг ответов QNetworkAccessManager в разных потоках
Отправлено: DrWebber от Январь 15, 2015, 06:03
Всем привет. Возник вопрос как правильно организовать обработку данных в разных потоках?

Есть вот такой класс(схематично):
Код:
class Requester : public QObject
{
    Q_OBJECT
    Requester () : QObject(0)
    {
        
         manager = new QNetworkAccessManager;

         QNetworkProxy proxy(QNetworkProxy::DefaultProxy, "127.0.0.1" , 5555);
         manager->setProxy(proxy);

         connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
    }

public:
    void sendGET(int id, const QNetworkRequest &req)
    {
      ......
        manager->get(req);
      ......
    }
    void sendPOST(int id, const QNetworkRequest &req, const QByteArray &data)
    {
      ......
        manager->post(req, data);
      ......
    }


private:

    QNetworkAccessManager *manager;

prinvate slots:
    void replyFinished(QNetworkReply* reply);



Экземпляров этого класса может быть....ну допустим 10. Я понимаю, что рекомендуется использовать 1 QNetworkAccessManager в приложении, но мне нужно чтобы запросы отправлялись через разные прокси.
Так же есть класс-генератор запросов, который вызывает функции sendGET и sendPOST, он работает в отдельном потоке QThread. Мне нужно чтобы данные, которые приходят в replyFinished обрабатывались параллельно, т.е. в данном случае в 10 потоков. Я пробовал создавать для каждого Requester свой QThread, но тогда я не мог вызывать из другого потока функции sendGET и sendPOST, вываливалось вот такое сообщение:
Код:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNetworkAccessManager(0x13963008), parent's thread is QThread(0x13962fe8), current thread is QThread(0x13962fb8)

Создавал вот так:
Код:
    Requester *requester = new Requester();

    QThread *thread = new QThread;
    requester->moveToThread(thread);

    QObject::connect(thread, SIGNAL(started()), requester, SLOT(startInThread()));

    QObject::connect(requester, SIGNAL(finished()), thread, SLOT(quit()));
    QObject::connect(requester, SIGNAL(finished()), requester, SLOT(deleteLater()));

    QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
    thread->start();


Когда так делал, класс Requester выглядел немного по-другому - вся инициализация была в функции startInThread().

Собственно вопрос: как такое правильно реализовать?


Название: Re: Парсинг ответов QNetworkAccessManager в разных потоках
Отправлено: Fregloin от Январь 15, 2015, 11:30
Код:
class Requester : public QObject
{
Q_OBJECT
...
public slots:
    void sendGET(int id, const QNetworkRequest &req) {..}
    void sendPOST(int id, const QNetworkRequest &req, const QByteArray &data) {...}
};

Ваши потоки должны быть как то так
Код:
class ReplyThread : public QThread {
...
signals:
    void sendGET(int id, const QNetworkRequest &req);
    void sendPOST(int id, const QNetworkRequest &req, const QByteArray &data);
}


Код:
    Requester *requester = new Requester(); //выполняется допустим в главном потоке, или в отдельном потоке

    ReplyThread *thread = new ReplyThread ; //выполняется в отдельном потоке
    QObject::connect(thread, SIGNAL(sendGET(...)), requester, SLOT(sendGet(...)));
    QObject::connect(thread, SIGNAL(sendPost(...)), requester, SLOT(sendPost(...)));
    thread->start();

Ну и конечно еще нужно вам в слоте SLOT(replyFinished(QNetworkReply*)) узнать какому потоку передать ответ (если потоки живут между ответами от сервера, а не создаются каждый раз)


Название: Re: Парсинг ответов QNetworkAccessManager в разных потоках
Отправлено: DrWebber от Январь 15, 2015, 12:17
А если вот так, без лишних классов? И за replyFinished переживать вроде не надо.

Код:
class Requester : public QObject
{
Q_OBJECT
.....
public slots:
    void startInThread()
    {
     .....
     QObject::connect(this, SIGNAL(sendGET(...)), this, SLOT(slot_sendGet(...)));
     QObject::connect(this, SIGNAL(sendPost(...)), this, SLOT(slot_sendPost(...)));

     QObject::connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
     .....
     }
...
private slots:
    void slot_sendGET(int id, const QNetworkRequest &req) {..}
    void slot_sendPOST(int id, const QNetworkRequest &req, const QByteArray &data) {...}

signals:
    void sendGET(int id, const QNetworkRequest &req);
    void sendPOST(int id, const QNetworkRequest &req, const QByteArray &data);
};

Ну и все это в QThread.
А потом где-то:
emit requester->sendGET(...);

Прокатит такой вариант, как считаете? Просто попробовать не могу сейчас.


Название: Re: Парсинг ответов QNetworkAccessManager в разных потоках
Отправлено: Fregloin от Январь 16, 2015, 12:54
то что вы написали будет работать в одном потоке, работать то оно будет конечно, но это не то что вы ожидаете. я же вам показал упрощенный код, который должен работать как надо, есть объект который содержит в себе QNAM, и есть куча порождаемых потоков, которые подключаются сигналами и слотами к одному экземпляру в QNAM, который живет в отдельном потоке.
Если сессия состоит из "запрос - ответ - поток - завершение потока" то проще наверное воспользоваться пулом потоков, когда на каждый ответ порождается новый поток, обрабатывается ответ и удалется поток.
Если поток должен жить между запросами и ответами, то нужно так же хранить мапу вида <QString,QThread> где ключ - это айпишник с которого пришёл ответ (или другой ключ известный вам идентифицирующий сессию), а QThread - ваш поток-обработчик.
Сответственно поток знает как отправить запрос, т.к. его сигнал sendGet/sendPost подключен к единственному экземпляру QNAM, а вот QNAM у нужно уже отправлять ответы отдельным потокам.
connect тут уже не прокатит, т.к. через него нельзя указать конкретный поток с конкреным сигналом (а точнее его содержимым). Тут нужно слать Евенты каждому конкретному потоку. Например так

Код:

QMap<QString,QThread*> sessionMap;

SLOT void replyfinished(reply)
{
  QString sessionKey = reply.getSessionKey(); //это вы должны подумать как идентифицировать сессии, проще всего какой то строкой в параметрах запроса
  QThread * replyProcessor = sessionMap.value(sessionKey); //ищем поток обработчик
  if(!replyProcessor) //если новая сессия
  {
   replyProcessor = new YourThhread(); //создаем обработчик
   connect(replyProcessor,SIGNAL(sendGet(..)),qnamOBject{объект в котором QNAM или непросредственно QNAM},SLOT(sendGet(..))); //соединяем обработчик что бы мог слать запросы
   sessionMap[sessionKey]=replyProcessor; //добавляем в мапу
  }
 
  YourEvent * e = new YourEvent(); //создаем событие
  e->setData(reply); //помещаем в него указатель на ответ
  sendEvent(replyProcessor,e); //шлем этому потоку (reply должен быть удалён в потоке обработчике через deleteLater после того как с него считано все что нужно)
}

в потоке обработчике нужно переопределить метод event()

Код:
bool YourThread::event(QEvent * e)
{
 if(e->type() == YourEvent_type)
 {
  YourEvent * ye = static_cast<YourEvent*>(e);
  QNetworkReply * reply = ye->replyPtr();
  data = reply.data(); //QByteArray если не ошибаюсь
  //данные считаны, следует удалить ответ от сервера нам
  reply->deleteLater();
  //обрабатываем данные
  ye->accept();
  return true; //показывем что это соыбтие уже дальше обрабатывать ненадо
 }
 else
  return QThread::event(e);
}