Open kelteseth opened 8 months ago
Hi,
thanks for the report. Indeed we allow constructing an empty invalid QCoro::Task
, so awaiting it shouldn't crash (not sure what it should do, though). I'll try to fix it, and probably deprecate the default constructor as it serves no real purpose. An empty QCoro::Task
makes little sense.
As a side note, you ran into the problem because you are using QCoro wrong (at least from what I can see from the minimal sample). Just returning a QmlTask
doesn't magically make your slowOperation()
function non-blocking. Your implementation will still block the main thread for 1s.
You need to have your "slow operation" implemented as a proper real coroutine, then you just wrap the returned QCoro::Task
(which represents the running coroutine) into QCoro::QmlTask
which makes it work in QML.
class TestQmlType : public QObject
{
Q_OBJECT
QML_ELEMENT
public:
explicit TestQmlType(QObject *parent = nullptr);
Q_INVOKABLE QCoro::QmlTask slowOperation();
private:
// A real coroutine with QCoro::Task<> return type
QCoro::Task<QVariant> doSlowOperation();
};
QCoro::QmlTask TestQmlType::slowOperation()
{
return QCoro::QmlTask(doSlowOperation());
}
QCoro::Task<QVariant> TestQmlType::doSlowOperation()
{
qInfo() << "Slow operation start";
co_await QCoro::sleepFor(1s);
qInfo() << "Slow operation end";
co_return 42;
}
Ahh, thanks! I was a bit confused by the docs then. Is there a way to move this into a single function?
return QCoro::QmlTask([]() -> QCoro::Task<QVariant> {
qInfo() << "Slow operation end";
co_return 42;
});
gives me:
C:\Users\Ryzen-7950X\Documents\CoroTest\TestQmlType.cpp(8): error C2440: '<function-style-cast>': cannot convert from 'TestQmlType::slowOperation::<lambda_1>' to 'QCoro::QmlTask'
C:\Users\Ryzen-7950X\Documents\CoroTest\TestQmlType.cpp(8): note: 'QCoro::QmlTask::QmlTask': no overloaded function could convert all the argument types
C:\Users\Ryzen-7950X\Documents\CoroTest\ThirdParty\qcoro\QCoro\qml\qcoroqmltask.h(37): note: could be 'QCoro::QmlTask::QmlTask(QCoro::Task<QVariant> &&)'
C:\Users\Ryzen-7950X\Documents\CoroTest\TestQmlType.cpp(8): note: 'QCoro::QmlTask::QmlTask(QCoro::Task<QVariant> &&)': cannot convert argument 1 from 'TestQmlType::slowOperation::<lambda_1>' to 'QCoro::Task<QVariant> &&'
C:\Users\Ryzen-7950X\Documents\CoroTest\TestQmlType.cpp(11): note: Reason: cannot convert from 'TestQmlType::slowOperation::<lambda_1>' to 'QCoro::Task<QVariant>'
C:\Users\Ryzen-7950X\Documents\CoroTest\TestQmlType.cpp(11): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
C:\Users\Ryzen-7950X\Documents\CoroTest\ThirdParty\qcoro\QCoro\qml\qcoroqmltask.h(68): note: or 'QCoro::QmlTask::QmlTask(QCoro::Task<void> &&)'
C:\Users\Ryzen-7950X\Documents\CoroTest\ThirdParty\qcoro\QCoro\qml\qcoroqmltask.h(59): note: or 'QCoro::QmlTask::QmlTask(T &&)'
C:\Users\Ryzen-7950X\Documents\CoroTest\ThirdParty\qcoro\QCoro\qml\qcoroqmltask.h(44): note: or 'QCoro::QmlTask::QmlTask(QCoro::Task<T> &&)'
C:\Users\Ryzen-7950X\Documents\CoroTest\TestQmlType.cpp(8): note: while trying to match the argument list '(TestQmlType::slowOperation::<lambda_1>)'
Also looks like the FetchContent_Populate I use is not fully correct, because I cannot include #include "QCoro/core/qcorotimer.h"
C:\Users\Ryzen-7950X\Documents\CoroTest\ThirdParty\qcoro\qcoro\core\qcorotimer.h:8: error: C1083: Cannot open include file: 'qcorocore_export.h': No such file or directory
Well, the documentation is far from perfect :-) I'll try to update it later...
You are on the right track, but you have to call the lambda so that you obtain the QCoro::Task<>
that can be passed into QmlTask
. In your case, the coroutine is synchronous (there's no co_await
) so when called it will run, finish, store its result into a QCoro::Task
and return that to you. You pass it to QmlTask
and QmlTask
will extract the result and provide it into QML (by invoking your .then() callback in JS).
If your lambda was a coroutine that suspends, then you would call it, the coroutine would run as a regular function until it would reach co_await
- at that point it would suspend and return the QCoro::Task
holding a reference to the suspended coroutine, which you then pass to QmlTask
again. Once the thing being awaiting is finished, the coroutine would be resumed and run to its completion, storing the result to the QCoro::Task
object you already have and that in turn would once again trigger the JS callback in your QML code.
Also looks like the FetchContent_Populate I use is not fully correct, because I cannot include #include "QCoro/core/qcorotimer.h"
Are you linking against the QCoro::Core target in your CMakeLists? That should pull in all the correct include paths.
Are you linking against the QCoro::Core target in your CMakeLists? That should pull in all the correct include paths.
Yes, this is from the attached test project: https://github.com/danvratil/qcoro/files/13634187/CoroTest.zip
EDIT: Ok looks like I was wrong. Normally I would expect the QCoro6::Qml to have an public dependency to QCoro6::Core :)
include(FetchContent)
FetchContent_Populate(
QCoro
GIT_REPOSITORY https://github.com/danvratil/qcoro.git
GIT_TAG 12c052e8e29b9f976ea1c02af7c845573bd8efa3
# Workaround because: 1. QtCreator cannot handle QML_ELEMENT stuff when it is in bin folder
# https://bugreports.qt.io/browse/QTCREATORBUG-27083
SOURCE_DIR ThirdParty/qcoro)
add_subdirectory(ThirdParty/qcoro)
# Does not work, needs to set(CMAKE_CXX_STANDARD 23)
qcoro_enable_coroutines()
qt_add_qml_module(appCoroTest
URI CoroTest
VERSION 1.0
QML_FILES Main.qml
SOURCES TestQmlType.h TestQmlType.cpp
)
target_link_libraries(appCoroTest
PRIVATE Qt6::Quick QCoro6::Qml
)
I tested this with Qt 6.6 on Windows 11 MSVC 2022 17.8
See my minimal implementation:
CoroTest.zip