• Playing with coroutines and Qt


    你好!
    我最近想知道C ++中的协程的状态,我发现了几个实现。我决定选择一个用于我的实验。
    它简单易用,适用于Linux和Windows。

    我的目标是试图找到一种方法来让代码异步运行,而不必等待信号触发插槽,并避免调用QCoreApplication :: processEvents或在堆栈中创建QEventLoop。

    我的第一种方法是将自定义事件调度程序的processEvent函数转换为协程并使用yield。几次失败后,我决定不继续这种方式。

    我的下一个尝试是将一个插槽转换为协程:

    1
    QTimer::singleShot(0, std::bind(&coroutine::resume, coroutine::create([]() { ... });

    在这个lambda中,CPU将执行代码直到yield,它将跳回到应用程序事件循环。
    完整的代码是:

    1
    2
    3
    4
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #include "coroutine.h"
    #include <QtCore>
    #include <QtWidgets>
     
    int main(int argc, char **argv)
    {
      QApplication app(argc, argv);
      QPushButton fibonacciButton("0: 0");
      fibonacciButton.show();
      QObject::connect(&fibonacciButton, &QPushButton::pressed,
                       std::bind(&coroutine::resume, coroutine::create([&]() {
        qulonglong f0 = 1, f1 = 0, n = 1;
        fibonacciButton.setText(QString("1: 1"));
        coroutine::yield();
        fibonacciButton.setText(QString("2: 1"));
        forever {
          auto next = f1 + f0;
          f0 = f1;
          f1 = next;
          fibonacciButton.setText(QString("%0: %1").arg(n++).arg(f0 + f1));
          coroutine::yield();
        }
      })));
      return app.exec();
    }

    在这里我们可以看到一个连接到一个lambda函数的按钮,它可以计算斐波那契数列中的数字。在计算下一个数字之后,我们调用yield,它将从该函数跳转到事件循环。当用户再次按下该按钮时,代码将在收益率之后返回到下一行

    这个例子的作用是因为用户需要再次按下按钮来恢复代码的执行。

    但是,有时我们想要自动恢复执行。为此,我们需要产生执行并安排执行的简历:

    1
    2
    3
    4
    6
    void qYield()
    {
      const auto routine = coroutine::current();
      QTimer::singleShot(0, std::bind(&coroutine::resume, routine));
      coroutine::yield();
    }

    第一行获取协程的标识符,第二行安排恢复。有了yield,CPU会回到以前的帧,最后到达主循环,并且无条件恢复代码。

    下一步是在情况发生时尝试恢复。Qt提供了指示什么时候发生的信号,所以更优化的执行方式是:

    1
    2
    3
    4
    6
    7
    8
    9
    template <typename Func>
    void qAwait(const typename QtPrivate::FunctionPointer::Object *sender, Func signal)
    {
      const auto routine = coroutine::current();
      const auto connection = QObject::connect(sender, signal,
                                               std::bind(&coroutine::resume, routine));
      coroutine::yield();
      QObject::disconnect(connection);
    }

    我们创建一个临时连接来恢复插槽的执行,而不是排队恢复。

    一个例子可以是:

    1
    2
    3
    4
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include "coroutine.h"
    #include <QtCore>
    #include <QtWidgets>
    #include <QtNetwork>
     
    int main(int argc, char **argv)
    {
      QApplication app(argc, argv);
      QPlainTextEdit textEdit;
      textEdit.show();
      QTimer::singleShot(0, std::bind(&coroutine::resume, coroutine::create([&]() {
        QUrl url("http://download.qt.io/online/qt5/linux/x64/online_repository/Updates.xml");
        QNetworkRequest request(url);
        QNetworkAccessManager manager;
        auto reply = manager.get(request);
        qAwait(reply, &QNetworkReply::finished);
        textEdit.setPlainText(reply->readAll());
        reply->deleteLater();
      })));
      return app.exec();
    }

    在这里,我创建了一个QTextEdit,它从互联网上接收文件的内容。当QNetworkReply完成时,数据被写入QTextEdit。

    另一个例子:

    1
    2
    3
    4
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #include "coroutine.h"
    #include <QtCore>
    #include <QtWidgets>
    #include <QtNetwork>
     
    int main(int argc, char **argv)
    {
      QApplication app(argc, argv);
      QPlainTextEdit edit;
      edit.show();
     
      QTimer::singleShot(0, std::bind(&coroutine::resume, coroutine::create([&]() {
        auto previousText = edit.toPlainText();
        forever {
          if (edit.toPlainText() == QStringLiteral("quit")) {
            qApp->quit();
          } else if (previousText != edit.toPlainText()) {
            qDebug() << previousText << "->" << edit.toPlainText();
            previousText = edit.toPlainText();
          }
          qAwait(&edit, &QPlainTextEdit::textChanged);
          qDebug() << "QPlainTextEdit::textChanged";
        }
      })));
      return app.exec();
    }

    每次用户修改文本时,该应用程序都会打印文本,并在用户写入“退出”字时完成执行。

    https://blog.qt.io/blog/2018/05/29/playing-coroutines-qt/

  • 相关阅读:
    利用快慢指针快速得到链表中间节点
    idea编译golang插件总结
    Jquery复习(二)之stop()易忘点
    Jquery复习(一)之animate()易忘点
    doT学习(三)之实战
    doT学习(二)之用法集合
    doT学习(一)之语法
    npm学习(十二)之高级用法
    npm学习(十一)之package-lock.json
    npm学习(十)之如何使用创建、发布、使用作用域包
  • 原文地址:https://www.cnblogs.com/findumars/p/9108027.html
Copyright © 2020-2023  润新知