Qt and coroutines
-
I have this class:
my_class.hpp
#ifndef MYCLASS_HPP #define MYCLASS_HPP #include <QObject> class MyClass : public QObject { Q_OBJECT public: MyClass(QObject* parent = nullptr); void doWork(); signals: void workFinished(); }; #endif // MYCLASS_HPP
And I need to achieve something like that:
void function() { MyClass myClass; qDebug() << "Doing the work..."; myClass.doWork(); // waiting until the workFinished() signal is emitted doSomethig(); qDebug() << "Work done"; }
Can I do that with coroutines? I don't want to fill the code with lambdas inside other lambdas.
Thank you -
-
@Fausto01 Sounds like you want to use coroutines because you want to use coroutines. Is it really worth it?
Anyway, it's not very Qt style, but if you really want to wait in the function until a signal comes just use local event loop. No lambdas or coroutines required.
MyClass myClass; myClass.doWork(); QEventLoop loop; connect(&myClass, &MyClass::workFinished, &loop, &QEventLoop::quit); loop.exec(); doSomethig();
You can wrap it in some function, so it's easier to reuse, e.g.
MyClass myClass; myClass.doWork(); waitFor(&myClass, &MyClass::workFinished); doSomethig();
Answering your question directly - coroutines on their own do not specify if they run on the same or another thread. that's up to the implementation of the task class. You'd have to have support structures that would run the coroutine in another thread and wake the caller in time to pump the message queue and process the signal. That's doable, but nothing like that exists out of the box in Qt, so you'd have to implement that yourself. Sounds to me like a whole can of worms for no gain.
-
@Chris-Kawa yeah I've tried the QEventLoop way, but sometimes it reprocesses the same event multiple times, I think that's because I used the event loop inside a slot from QML. Is it possible?
-
Hi,
Can you explain what you want to achieve ?
From the outside, it seems you want to do some blocking parallelism which usually defeats the purpose of parallelism. -
@SGaist my bad if I did not explain it clearly, my goal is to write the code as if it was synchronous, but it is asynchronous instead.
I took inspiration from QCoro library but I cannot use it.
I want the code to be linear as the QEvenLoop example, but I would like to not use it. -
@SGaist something like that, where the get() function runs asynchronously
QNetworkAccessManager networkAccessManager; // co_await the reply - the coroutine is suspended until the QNetworkReply is finished. // While the coroutine is suspended, *the Qt event loop runs as usual*. const QNetworkReply *reply = co_await networkAccessManager.get(url); // Once the reply is finished, your code resumes here as if nothing amazing has just happened ;-) const auto data = reply->readAll();
-
@Fausto01 The problem is that you can't just suspend the execution if you're in the main thread. It needs to pump the event loop or your app will freeze. If you're not in the main thread you can suspend, but you still need an active event loop to process the finish signal and if you're blocking you might as well just block explicitly and not use coroutines.
I think you need to explain your use case a bit more. Where do you start the "task" - main thread or a worker. What do you want to happen with that thread - do you want it to block or keep processing events.
In general Qt model is event based - it needs an active message pump that processes incoming signals or system events. Coroutines are not really suited for that I think. Sure you can force any code into using them, but it kinda works against the design of the library. "I want the code to be linear" is not a good idea when the library is not designed to be used like that. You're not gonna get better code forcing the library into a pattern it wasn't designed for.
I've tried the QEventLoop way, but sometimes it reprocesses the same event multiple times,
That... doesn't sound plausible. There's really no magic to it. It's just a simple message queue. More likely you had a bug in your code, but I can't say for sure without more details.
-
@Fausto01 said in Qt and coroutines:
something like that, where the get() function runs asynchronously
I guess you need to further define what you mean with "asynchronously". Your example shows network communication. One of the best examples for this sort of asynchronicity is the asio lib. Another good example is signals/slots in Qt (yes, this is called asynchronous!). Both of these don't allow for a linear flow of logic in your code.
C++'s coroutines are still in its infancy. I do understand that it would be nice to use co_await wherever you are in your code. It is impossible to use Qt's classes, like QNetworkAccessManager, directly with coroutines. You would have to write your own class and wrap this. It does not seem to be possible to generalize coroutines for anything else. What would have to happen for the QNetworkAccessManager is something like this:
- Call QNetworkAccessManager::get(url).
- Create a local event loop.
- Connect QNetworkReply::finished() to quit the local event loop.
- Run the local event loop.
Certainly the local event loop part can be generalized, but connecting the right signal to finish the event loop can not.
Maybe, this way of thinking (in the context of Qt) is also totally backwards. Your own function (creating a QNetworkAccessManager, getting a QNetworkReply, reading the data) should be a coroutine itself. After calling QNetworkAccessManager::get() you would co_yield to return to the main event loop. But before that you need to connect QNetworkReply::finished() to a slot which will then call your coroutine again in order to continue after the co_yield. This would truly allow for linear code on your side. This approach should work with a generalized coroutine which handles interfacing the coroutine with the Qt event loop. The only thing left on the programmers side would be to connect a single signal to your own generalized coroutine's slot which would resume the coroutine.