如何在Qt中正确中止网络请求

How to properly abort a network request in Qt?

本文关键字:网络 请求 Qt      更新时间:2023-10-16

我有一个类,它执行对网络的请求并解析数据。如何正确实现请求中止?

想象一下,我有这样一个班级:

class MyClass
{
public:
...
    void doRequest()
    {
        m_reply = m_manager.get(...);
        QEventLoop waitForResponse;
        connect(m_reply, &QNetworkReply::finished, &waitForResponse, &QEventLoop::quit);
        waitForResponse.exec();
        // Check if request was aborted (otherwise, application will crash)
        if (m_reply == nullptr)
            return;
        // Check for network errors, write result to m_data and delete m_reply;
        ...
    }
    void abort()
    {
        if (m_reply != nullptr)
            m_reply->abort();
    }
    QString data()
    {
        return m_data;
    }
...
private:
    QNetworkAccessManager *m_manager;
    QPiinter<QNetworkReply> m_reply;
    QString m_data;
}

以下是按下按钮的使用示例:

class MainWindow : public QMainWindow
{
...
private slots:
    MainWindow::on_myButton_pressed()
    {
        m_myClass->abort();
        m_myClass->doRequest();
        ui->myTextEdit->setText(m_myClass->data());
    }
private:
    MyClass m_myClass;
}

当您按下按钮时,如果上一个请求未完成,则会取消该请求。这行得通。但在这种情况下,新请求将数据写入QTextEdit并退出函数,然后旧请求从它自己的循环返回并再次将相同的m_data写入QTextEdit

是合适的吗?也许有更正确的方法来实现这一点?

嵌套事件循环是万恶之源。编写像doRequest这样的函数要简单得多,而无需向用户假装它是一个同步函数。您似乎已经跟踪了调用abort()时发生的复杂控制流,并且您了解了对doRequest()的后续调用如何由于重新进入事件循环而最终成为嵌套调用。如果多次重新启动请求,堆栈将如下所示(堆栈向下增长(:

1.  main function
2.  main event loop
3.  [...] (Qt functions)
4.  MainWindow::on_myButton_pressed()
5.  MyClass::doRequest()
6.  QEventLoop::exec()
7.  [...] (Qt functions)
8.  MainWindow::on_myButton_pressed()
9.  MyClass::doRequest()
10. QEventLoop::exec()
11. [...] (Qt functions)
12. MainWindow::on_myButton_pressed()   and so on...

每个对MainWindow::on_myButton_pressed()调用都需要在其嵌套事件循环退出时调用ui->myTextEdit->setText()

另一种方法是使您的函数完全异步,如果您需要在特定操作完成时执行内容,则依赖于信号/插槽。以下是您要实现的目标的最小实现:

#include <QtNetwork>
#include <QtWidgets>
/// A class responsible for communication with the web backend
class Backend : public QObject {
  Q_OBJECT
public:
  explicit Backend(QObject *parent = nullptr)
      : QObject(parent), m_reply(nullptr) {}
public slots:
  void doRequest() {
    // abort and delete ongoing request (if any)
    if (m_reply) {
      m_reply->abort();
      delete m_reply;
      m_reply = nullptr;
    }
    emit textUpdated(QString());
    // send request
    QUrl url("http://wtfismyip.com/text");
    QNetworkRequest request(url);
    m_reply = m_manager.get(request);
    // when the request is finished,
    QObject::connect(m_reply, &QNetworkReply::finished, [this] {
      // if the request ended successfully, read the received ip into m_lastData
      if (m_reply->error() == QNetworkReply::NoError)
        m_lastData = QString::fromUtf8(m_reply->readAll());
      // otherwise, emit errorOccured() signal (if the request has not been
      // actively canceled)
      else if (m_reply->error() != QNetworkReply::OperationCanceledError)
        emit errorOccured(m_reply->errorString());
      // in all cases, emit updateText and do cleanup
      emit textUpdated(m_lastData);
      m_reply->deleteLater();
      m_reply = nullptr;
    });
  }
  void abort() {
    if (m_reply != nullptr)
      m_reply->abort();
  }
signals:
  void textUpdated(const QString &);
  void errorOccured(const QString &);
private:
  QNetworkAccessManager m_manager;
  QNetworkReply *m_reply;
  QString m_lastData;
};
/// A minimal widget that contains a QPushButton and a QLabel
class Widget : public QWidget {
  Q_OBJECT
public:
  explicit Widget(QWidget *parent = nullptr) : QWidget(parent) {
    m_layout.addWidget(&m_pushButton);
    m_layout.addWidget(&m_label);
    connect(&m_pushButton, &QPushButton::clicked, this, &Widget::buttonClicked);
  }
signals:
  void buttonClicked();
public slots:
  void updateText(const QString &text) { m_label.setText(text); }
  void showError(const QString &error) {
    QMessageBox::warning(this, tr("Error"), error);
  }
private:
  QVBoxLayout m_layout{this};
  QPushButton m_pushButton{"Retrieve Name"};
  QLabel m_label;
};
int main(int argc, char *argv[]) {
  QApplication a(argc, argv);
  Backend backend;
  Widget widget;
  // connect components
  QObject::connect(&backend, &Backend::textUpdated, &widget,
                   &Widget::updateText);
  QObject::connect(&backend, &Backend::errorOccured, &widget,
                   &Widget::showError);
  QObject::connect(&widget, &Widget::buttonClicked, &backend,
                   &Backend::doRequest);
  widget.show();
  return a.exec();
}
#include "main.moc"