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/

猜你喜欢

转载自www.cnblogs.com/findumars/p/9108027.html