danvratil / qcoro

C++ Coroutines for Qt
https://qcoro.dvratil.cz
MIT License
326 stars 53 forks source link

ASSERT: "this->mAwaitedCoroutine != nullptr" when awaiting a default-constructed Task #200

Open kelteseth opened 8 months ago

kelteseth commented 8 months ago

I tested this with Qt 6.6 on Windows 11 MSVC 2022 17.8

11:31:06: Starting C:\Users\Ryzen-7950X\Documents\build-CoroTest-Desktop_Qt_6_6_0_MSVC2019_64bit-Debug\appCoroTest.exe...
Slow operation start
Slow operation end
ASSERT: "this->mAwaitedCoroutine != nullptr" in file C:\Users\Ryzen-7950X\Documents\CoroTest\ThirdParty\qcoro\qcoro\impl/task.h, line 71

image

See my minimal implementation:

CoroTest.zip

danvratil commented 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;
}
kelteseth commented 8 months ago

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
danvratil commented 8 months ago

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.

kelteseth commented 8 months ago

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
)