Consider I have the following asynchronous task runner. It puts tasks in a queue and executes them one by one, calling lambdas on completion. If func yields result of some type, then onComplete callback should receive argument of the same type.
class TaskRunner
{
public:
TaskRunner() :
mPool(new QThreadPool())
{
mPool->setMaxThreadCount(1);
mPool->setExpiryTimeout(-1);
}
public:
// For Funcs returning an instance of std::variant
template <typename Func, typename OnComplete, typename OnError, typename... Args>
std::enable_if_t<is_instance<std::invoke_result_t<Func, Args...>, std::variant>::value>
async(Func func, OnComplete onComplete, OnError onError, Args... args)
{
QMutexLocker lock(&mutex);
(void)QtConcurrent::run(mPool.get(), [=]() {
const auto result = func(args...);
if (result.index() == 0)
onComplete(std::get<0>(result));
else
onError(std::get<1>(result));
});
}
// ... similar templates for std::optional, etc. ...
private:
QScopedPointer<QThreadPool> mPool;
QMutex mutex;
};
Usage:
TaskRunner runner;
runner.async([](int a, int b) {
if(b != 0)
return a/b;
else
return std::string("division by zero");},
[](auto result){ std::cout << result << '\n'; },
[](auto error){ std::cout << error << '\n'; },
10, 2);
The problem is onComplete and onError callbacks should be called from the main thread. I've heard this could be achieved via Qt's signals and slots system, but I am unable to figure out the solution, let alone implement it.
An easy way to do what you are asking is with
QtConcurrent::runand an object.The way it goes is:
QtConcurrent::run. What you get from that is aQFuturethat you can inquire to get the execution status.As you will see in the documentation, you can make that work with a thread pool created beforehand.
QFuture::waitForFinished: it will block your thread until the execution is done.This can be useful if you are already in a worker thread, starting tasks in other worker threads; however, it is safe to say you should never want to call it in the main thread and block it.
Here is a quick example that runs 5x a function in worker threads. A
taskNo(from 1 to 5) is passed from the main thread to the worker thread inQtConcurrent::runand back in signals.The tasks basically consist in printing the id of the thread executing them. There are also a number of lines that print what is executed in the main thread.
For illustration's sake, this example works with a single worker object.
You can work with separate worker objects though, but you'll need to create pointers inside the
forloop, change their thread affinity inQtConcurrent::run(instead of just printing to the screen like I did), do the connection and only then start the work.In any case, always explicitly mark your connection with
Qt::QueuedConnection. That may be whatQObject::connectwill do by default but at least, it makes your intention clear.In the
mainfunction, I have put comments in 3 places for illustration:taskCompletedsignal is emitted.main. I have made it so that it is not used though, that is unless you uncomment it.BTW, pay close attention to the thread that executes
QtConcurrent::runvsQFuture::thenand how tasks won't start until theQFuturechain is over (you can make it more obvious addingQThread::msleep(1000);inWorker::run).QFuture::waitForFinisheddoes. If you were to uncomment it, work would still be done in the worker thread but with no parallelism: since it is blocking the main thread before it can create the next worker thread (see my comment above).Worker.hWorker.cppmain.cpp