Skip to content

Commit

Permalink
QCoroThread: implement wrapper for QThread
Browse files Browse the repository at this point in the history
A coroutine-friendly wrapper to co_await a thread to start or finish.
  • Loading branch information
danvratil committed Apr 24, 2022
1 parent 5180642 commit 832d931
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 1 deletion.
17 changes: 17 additions & 0 deletions docs/examples/qthread.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <QCoroThread>
#include <QThread>

#include <memory>

QCoro::Task<void> MainWindow::processData(const QVector<qint64> &data) {
std::unique_ptr<QThread> thread(QThread::create([data]() {
// Perform some intesive calculation
}));
thread->start();

ui->setState(tr("Processing is starting..."));
co_await qCoro(thread.get()).waitForStarted();
ui->setState(tr("Processing data..."));
co_await qCoro(thread.get()).waitForFinished();
ui->setState(tr("Processing done."));
}
63 changes: 63 additions & 0 deletions docs/reference/core/qthread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!--
SPDX-FileCopyrightText: 2022 Daniel Vrátil <dvratil@kde.org>
SPDX-License-Identifier: GFDL-1.3-or-later
-->

# QThread

{{ doctable("Core", "QCoroThread", ("core/qthread", "QCoroThread")) }}

[`QThread`][qtdoc-qthread] has two events: `started` and `finished`. QCoro provides
a coroutine-friendly wrapper for `QThread` - `QCoroThread`, which allows `co_await`ing
those events.

To wrap a `QThread` object into the `QCoroThread` wrapper, use [`qCoro()`][qcoro-coro]:

```cpp
QCoroThread qCoro(QThread &);
QCoroThread qCoro(QThread *);
```
## `waitForStarted()`
Waits for the thread to start. Returns `true` if the thread is already running
or when the thread starts within the specified timeout. If the thread has already
finished or fails to start within the specified timeout, the coroutine will return
`false`.
If the timeout is set to -1, the operation will never time out.
See documentation for [`QThread::started()`][qtdoc-qthread-started] for details.
```cpp
QCoro::Task<bool> QCoroThread::waitForStarted(std::chrono::milliseconds timeout);
```

## `waitForFinished()`

Waits for the Waits for the process to finish or until it times out. Returns `bool` indicating
whether the process has finished successfuly (`true`) or timed out (`false`).
thread to finish. Returns `true` if the thread has already finished
or if it finishes within the specified timeout. If the thread has not started yet
or fails to stop within the specified timeout the coroutine will return `false`.

If the timeout is set to -1, the operation will never time out.

See documentation for [`QThread::finished()`][qtdoc-qthread-finished] for details.

```cpp
QCoro::Task<bool> QCoroThread::waitForFinished(std::chrono::milliseconds timeout);
```
## Examples
```cpp
{% include "../../examples/qthread.cpp" %}
```


[qtdoc-qthread]: https://doc.qt.io/qt-5/qthread.html
[qtdoc-qthread-waitForStarted]: https://doc.qt.io/qt-5/qthread.html#waitForStarted
[qtdoc-qthread-waitForFiished]: https://doc.qt.io/qt-5/qthread.html#waitForFinished
[qcoro-coro]: ../coro/coro.md
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ nav:
- QFuture: reference/core/qfuture.md
- QIODevice: reference/core/qiodevice.md
- QProcess: reference/core/qprocess.md
- QThread: reference/core/qthread.md
- QTimer: reference/core/qtimer.md
- Network:
- reference/network/index.md
Expand Down
3 changes: 2 additions & 1 deletion qcoro/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@ add_qcoro_library(
qcoroiodevice.cpp
qcoroiodevice_p.cpp
qcoroprocess.cpp
qcorothread.cpp
qcorotimer.cpp
CAMELCASE_HEADERS
QCoroCore
QCoroIODevice
QCoroProcess
QCoroSignal
QCoroThread
QCoroTimer
QCoroFuture
QT_LINK_LIBRARIES
PUBLIC Core
QCORO_LINK_LIBRARIES
PUBLIC Coro
)

38 changes: 38 additions & 0 deletions qcoro/core/qcorothread.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2022 Daniel Vrátil <dvratil@kde.org>
//
// SPDX-License-Identifier: MIT

#include "qcorothread.h"
#include "qcorosignal.h"

#include <QThread>

using namespace QCoro::detail;

QCoroThread::QCoroThread(QThread *thread)
: mThread(thread)
{}

QCoro::Task<bool> QCoroThread::waitForStarted(std::chrono::milliseconds timeout) {
if (mThread->isRunning()) {
co_return true;
}
if (mThread->isFinished()) {
co_return false;
}

const auto result = co_await qCoro(mThread.data(), &QThread::started, timeout);
co_return result.has_value();
}

QCoro::Task<bool> QCoroThread::waitForFinished(std::chrono::milliseconds timeout) {
if (mThread->isFinished()) {
co_return true;
}
if (!mThread->isRunning()) {
co_return false;
}

const auto result = co_await qCoro(mThread.data(), &QThread::finished, timeout);
co_return result.has_value();
}
64 changes: 64 additions & 0 deletions qcoro/core/qcorothread.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2022 Daniel Vrátil <dvratil@kde.org>
//
// SPDX-License-Identifier: MIT

#pragma once

#include <QPointer>

class QThread;

namespace QCoro {
template<typename T>
class Task;
} // namespace QCoro

namespace QCoro::detail {

class QCoroThread {
public:
explicit QCoroThread(QThread *thread);

/**
* \brief Coroutine that waits for a thread to get started.
*
* \return Returns `true` when the thread is already running or when it starts within the
* specified timeout. If the thread has already finished, or the operation times out, the
* coroutine returns `false`.
*
* If the timeout is -1 the operation will never time out.
*
* See [`QThread::started()`][qtdoc-qthread-started] documentation for details.
*
* [qtdoc-qthread-started]: https://doc.qt.io/qt-5/qthread.html#started
*/
Task<bool> waitForStarted(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1});

/**
* \brief Coroutine that waits for a thread to finish.
*
* \return Returns `true` when the thread has already finished or when it finishes within
* specified timeout. If the thread is not running and hasn't finished yet, or when the
* operation times out, the coroutine returns `false`.
*
* If the timeout is -1 the operation will never time out.
*
* See [`QThread::finished()`][qtdoc-qthread-finished] documentation for details.
*
* [qdoc-qthread-finished]: https://doc.qt.io/qt-5/qthread.html#finished
*/
Task<bool> waitForFinished(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1});

private:
QPointer<QThread> mThread;
};

} // namespace QCoro::detail

inline auto qCoro(QThread *thread) {
return QCoro::detail::QCoroThread(thread);
}

inline auto qCoro(QThread &thread) {
return QCoro::detail::QCoroThread(&thread);
}
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ endfunction()
qcoro_add_test(qtimer)
qcoro_add_test(qcoroprocess)
qcoro_add_test(qcorosignal)
qcoro_add_test(qcorothread)
qcoro_add_test(task)
qcoro_add_test(testconstraints)
qcoro_add_test(qfuture LINK_LIBRARIES Qt${QT_VERSION_MAJOR}::Concurrent)
Expand Down
58 changes: 58 additions & 0 deletions tests/qcorothread.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2022 Daniel Vrátil <dvratil@kde.org>
//
// SPDX-License-Identifier: MIT

#include "testobject.h"

#include "qcoro/core/qcorothread.h"
#include "qcoro/core/qcorosignal.h"

#include <QThread>
#include <QScopeGuard>

#include <thread>
#include <memory>

using namespace std::chrono_literals;

class QCoroThreadTest : public QCoro::TestObject<QCoroThreadTest> {
Q_OBJECT

private:
QCoro::Task<> testWaitForStarted_coro(QCoro::TestContext) {
std::unique_ptr<QThread> thread(QThread::create([]() {
std::this_thread::sleep_for(100ms);
}));

QScopeGuard guard([&]() { thread->wait(); });

QCORO_DELAY(thread->start());

const bool ok = co_await qCoro(thread.get()).waitForStarted();
QCORO_VERIFY(thread->isRunning());
QCORO_VERIFY(ok);
}

QCoro::Task<> testWaitForFinished_coro(QCoro::TestContext) {
std::unique_ptr<QThread> thread(QThread::create([]() {
std::this_thread::sleep_for(100ms);
}));

thread->start();
co_await qCoro(thread.get()).waitForStarted();
QCORO_VERIFY(thread->isRunning());
const bool ok = co_await qCoro(thread.get()).waitForFinished();
QCORO_VERIFY(thread->isFinished());

QCORO_VERIFY(ok);
}

private Q_SLOTS:
addTest(WaitForStarted)
addTest(WaitForFinished)
};


QTEST_GUILESS_MAIN(QCoroThreadTest)

#include "qcorothread.moc"

0 comments on commit 832d931

Please sign in to comment.