LINUX.ORG.RU

Не работает выход из консольного Qt приложения

 , , , ,


0

0

Не завершается консольное приложение в функции App::replyError(), хотя при ошибке в функцию заходит, и сообщение выводится. В App::run() все нормально.

PS: Делаю первые попытки в Qt. Сильно говнокодно?

project.pro

CONFIG += console
QT = core network

HEADERS = \
  app.h
SOURCES = main.cpp \
  app.cpp \

main.cpp

#include <QtCore>

#include "app.h"

int main(int argc, char *argv[])
{
  QCoreApplication application(argc, argv);
  App app;
  QTimer::singleShot(0, &app, &App::run);

  return application.exec();
}

app.h

#ifndef APP_H
#define APP_H

#include <QtCore>
#include <QtNetwork>

class App : public QObject
{
  Q_OBJECT
public:
  App(QObject *parent = 0);
public slots:
  void run();
private:
  QNetworkAccessManager *manager;
  QString token;
  void getToken();
  void replyError(QNetworkReply::NetworkError code);
};

#endif

app.cpp

#include <QDebug>

#include "app.h"

const QString battleNetClientId = "battleNetClientId";
const QString battleNetClientSecret = "battleNetClientSecret";

App::App(QObject *parent) : QObject(parent)
{
  manager = new QNetworkAccessManager(this);
}

void App::run()
{
  getToken();
  qDebug() << token;

  QCoreApplication::quit();
}

void App::getToken()
{
  const QString authorizationData = QString("%1:%2").arg(battleNetClientId).arg(battleNetClientSecret);
  const QString headerData = QString("Basic %1").
    arg(QString::fromStdString(authorizationData.toLatin1().toBase64().toStdString()));

  QNetworkRequest request;
  request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
  request.setRawHeader("Authorization", headerData.toLatin1());
  request.setUrl(QUrl("https://eu.battle.net/oauth/token"));

  QUrlQuery postData;
  postData.addQueryItem("grant_type", "client_credentials");
  postData.addQueryItem("scope", "wow.profile");
  QNetworkReply *reply = manager->post(request, postData.toString(QUrl::FullyEncoded).toLatin1());
  connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &App::replyError);

  QEventLoop loop;
  connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
  loop.exec();

  QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
  QJsonObject object = document.object();
  token = object["access_token"].toString();

  reply->deleteLater();
}

void App::replyError(QNetworkReply::NetworkError code)
{
  qCritical() << code;
  QCoreApplication::exit(1);
}

Рискну предположить, что цикл обработки сообщений не успел стартануть до вызова replyError, хотя, по идее так быть не должно и это похоже на баг.

Если тебе ехать - сделай через postEvent, если шашечки - то смотри код exit и код exec. exit где-то должен проверять что «ивент луп запущен».

pon4ik ★★★★★ ()

Есть ещё вероятность, что разработчики таймера считерили и сделали для сна 0 вызов в обход цикла событий. Это в любом случае баг, но попробуй выставить таймаут у таймера в единицу.

pon4ik ★★★★★ ()

Сильно говнокодно?

ну, так.. нормальненько :-D

зачем ты еще один евентлуп себе нагородил?

вот тут:

QEventLoop loop;
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();

вот эта штука return application.exec(); уже запускает основной вентлуп и, в общем случае, тебе другой такой не нужен.

aol ★★★★★ ()
Ответ на: комментарий от aol

Спасибо, вы дали направление для решения проблемы. @pon4ik, как оказалось никакого бага нет.

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

Суть в том что его нужно останавливать внутри App::replyError(), так как он продолжает работать и блокирует завершение программы.

Вопрос решен!

Xoomoh ()
Ответ на: комментарий от Xoomoh

Можно и без него, конечно.

смотря, какая цель. если программу запутать и потом жаловаться, что «кляты кутешники», то лучше оставить так :)

но зачем в синхронном?

aol ★★★★★ ()
Ответ на: комментарий от aol

Так я и не жаловался. Делаю первые пробы пера с использованием Qt. Немного запутался, но вы подсказали что не так. Проблему решил.

А в синхронном потому что программе не нужно ничего делать пока выполняется сетевой запрос, просто дождаться его завершения и двигаться дальше. Примерно как curl работает. Можно было бы реализовать что-то вроде машины состояний, но в моем случае это лишняя сущность.

Если бы это приложение имело какой-либо интерфейс, например Gui, тогда да, блокировать не стоило бы. В любом случае я очень благодарен за помощь.

Xoomoh ()
Ответ на: комментарий от Xoomoh

Не, просто QApplication::exec уже создаёт QEventLoop общий на всё приложение. В Gui приложении ты бы разделял i/o события с событиями своей логики в этом цикле, но в данном случае смысла создавать ещё один цикл нет, ну или я не понял задумку. Этот как io_service создавать в тех точках, где ты его создаёшь.

pon4ik ★★★★★ ()
Ответ на: комментарий от Xoomoh

Т.е. достаточно просто сделать вот так:

-connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+connect(reply, &QNetworkReply::finished, QCoreApplication::instance(), &QCoreApplication::quit);

А все упоминания eventloop просто удалить.

pon4ik ★★★★★ ()
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от pon4ik

Задумка в том что приложение должно дождаться сигнала QNetworkRequest::finished, а в это время просто ждать, причем не выходя из функции в которой делался этот запрос.

Незнаю как еще объяснить. Некий логический аналог while(1);.

Есть синхронные сетевые запросы, которые блокируют программу до завершения, а есть асинхронные, когда работа программы продолжается до наступления события завершения сетевого запроса.

Xoomoh ()
Ответ на: комментарий от pon4ik
connect(reply, &QNetworkReply::finished, QCoreApplication::instance(), &QCoreApplication::quit);

Таким образом работа приложения завершится после завершения сетевого запроса, и все приложение будет бесполезно.

Весьма странно завершать приложение так. Ведь сетевой ответ нужно дальше как-то использовать.

Xoomoh ()
Ответ на: комментарий от pon4ik

А если хочется прямо синхронно синхронно, то QNetworkReply::waitForReadyRead твой друг.

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

aol ★★★★★ ()
Ответ на: комментарий от Xoomoh

В общем надо либо труселя скидывать, либо крестик надевать. Проги коотрые миксуют синхронный и асинхронный подход на одном уровне абстракции выглядят странно и удивляют наблюдателя.

pon4ik ★★★★★ ()
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от Xoomoh
Ответ на: комментарий от pon4ik

хотя, по идее так быть не должно и это похоже на баг.

Наверное singleShot с 0 делает DirectConnection в этом же треде.

Поменяй

QTimer::singleShot(0, &app, &App::run);

на

QMetaObject::invokeMethod(&app,«run»,Qt::QueuedConnection);

anonymous ()
Ответ на: комментарий от Xoomoh

Лишнее, это QEventLoop.

Чет появилось ощущение что ты троллишь, оно и с самого начала было, но теперь стократно усилилось.

По крайней мере итоговый вариант на свободных функциях ощутимо более уродский. Например ты создаёшь приложение но никак его не используешь.

pon4ik ★★★★★ ()
Ответ на: комментарий от pon4ik

Да не скучно стало. Запутался уже как красиво это решить. Если можешь помочь буду рад и бескрайние благодарен. Если есть желание можно обсудить, к примеру, в телеге. Ник как на форуме.

Xoomoh ()