18

The QFuture class has methods such as cancel(), progressValue(), etc. These can apparently be monitored via a QFutureWatcher. However, the documentation for QtConcurrent::run() reads:

Note that the QFuture returned by QtConcurrent::run() does not support canceling, pausing, or progress reporting. The QFuture returned can only be used to query for the running/finished status and the return value of the function.

I have looked in vain for what method actually can create a QFuture that can be cancelled and report progress for a single long-running operation. (It looks like maybe QtConcurrent::map() and similar functions can, but I just have a single, long-running method.)

(For those familiar with .Net, something like the BackgroundWorker class.)

What options are available?

Gaurav Dave
  • 6,838
  • 9
  • 25
  • 39
Dave Mateer
  • 17,608
  • 15
  • 96
  • 149
  • QFuture does seem partially implemented. Sounds like they were going for something more detailed, and stoped early. The KDE guys added ThreadWeaver: http://api.kde.org/frameworks-api/frameworks5-apidocs/threadweaver/html/index.html. However with std::thread and std::async now in the standard (C++11), they seem to be better alternatives to QThread, and QtConcurrentRun. – Atif Mar 07 '16 at 15:23

5 Answers5

23

Though it's been a while since this question was posted and answered I decided to add my way of solving this problem because it is rather different from what was discussed here and I think may be useful to someone else. First, motivation of my approach is that I usually don't like to invent own APIs when framework already has some mature analogs. So the problem is: we have a nice API for controlling background computations represented by the QFuture<>, but we have no object that supports some of the operations. Well, let's do it. Looking on what's going on inside QtConcurrent::run makes things much clearer: a functor is made, wrapped into QRunnable and run in the global ThreadPool.

So I created generic interface for my "controllable tasks":

class TaskControl
{
public:
    TaskControl(QFutureInterfaceBase *f) : fu(f) {  }
    bool shouldRun() const { return !fu->isCanceled(); }
private:
    QFutureInterfaceBase *fu;
};

template <class T>
class ControllableTask
{
public:
    virtual ~ControllableTask() {}
    virtual T run(TaskControl& control) = 0;
};

Then, following what is made in qtconcurrentrunbase.h I made q-runnable for running this kind of tasks (this code is mostly from qtconcurrentrunbase.h, but slightly modified):

template <typename T>
class RunControllableTask : public QFutureInterface<T> , public QRunnable
{
public:
    RunControllableTask(ControllableTask<T>* tsk) : task(tsk) { }
    virtial ~RunControllableTask() { delete task; }

    QFuture<T> start()
    {
        this->setRunnable(this);
        this->reportStarted();
        QFuture<T> future = this->future();
        QThreadPool::globalInstance()->start(this, /*m_priority*/ 0);
        return future;
    }

    void run()
    {
        if (this->isCanceled()) {
            this->reportFinished();
            return;
        }
        TaskControl control(this);
        result = this->task->run(control);
        if (!this->isCanceled()) {
            this->reportResult(result);
        }
        this->reportFinished();
    }

    T  result;
    ControllableTask<T> *task;
};

And finally the missing runner class that will return us controllable QFututre<>s:

class TaskExecutor {
public:
    template <class T>
    static QFuture<T> run(ControllableTask<T>* task) {
        return (new RunControllableTask<T>(task))->start();
    }

};

The user should sublass ControllableTask, implement background routine which checks sometimes method shouldRun() of TaskControl instance passed to run(TaskControl&) and then use it like:

QFututre<int> futureValue = TaskExecutor::run(new SomeControllableTask(inputForThatTask));

Then she may cancel it by calling futureValue.cancel(), bearing in mind that cancellation is graceful and not immediate.

Hatter
  • 773
  • 1
  • 6
  • 12
  • 1
    Sounds similar to Threadweaver: http://api.kde.org/frameworks-api/frameworks5-apidocs/threadweaver/html/index.html Have you taken a look at that library? – Atif Mar 07 '16 at 15:21
  • 1
    @Atif: No, I haven't until you pointed. Yes, it looks similar, however ThreadWeaver looks much more elaborate and probably would be a good option if I had requirements about how these tasks should depend on each other. In my specific case I reached the goal by crafting a couple of dozens lines using Qt only, and it was good enough not to pull dependencies. – Hatter Mar 09 '16 at 08:05
  • **Yup.** Pulling in the entirety of KDE only for access to a single class is insanity – unlike, say, this compact solution. If you already require KDE, Threadweaver may be the obvious goto. In most cases, however, you just want to reinvent their wheel. (*What? It's a small wheel.*) – Cecil Curry Apr 10 '18 at 05:36
  • 1
    While sublime for those at the C++ layer, this solution sadly fails to scale to Python. Between the low-level templating and gratuitous use of private, undocumented APIs (e.g., `QFutureInterface`, `QFutureInterfaceBase`), Pythonistas have little choice but to reimplement the `QFuture` API manually in `QRunnable` subclasses. `` – Cecil Curry Apr 10 '18 at 05:57
  • 1
    @CecilCurry In fact, KF5 libraries are designed to work independent from each other so you will *not* pull "the entirety of KDE" but just a simple library like any other into your project. It is a simple dependency to ThreadWeaver _only_. ThreadWeaver only depends on Qt as it should. – ypnos Jan 18 '19 at 16:54
  • Hi! @Hatter! Probably it would be a more completed and elegant decision to provide some notes about HOW is processing different number of input parameters for runnable task? In the "real")) Qt code for QtConcurrent::run(...) function there are at least 6 predefined variants of this func. with 0/1/2/3/4/5 acceptable arguments which in turn are at the right time will be passed directly as arguments for that "RUN"-function which indeed must perform "long-term" operation in a separate thread. Maybe some FULL & compilable example from github repo will say more rather than only code templates? – graphElem Jul 23 '21 at 15:13
3

I tackled this precise problem a while ago, and made something called "Thinker-Qt"...it provides something called a QPresent and a QPresentWatcher:

http://hostilefork.com/thinker-qt/

It's still fairly alpha and I've been meaning to go back and tinker with it (and will need to do so soon). There's a slide deck and such on my site. I also documented how one would change Mandelbrot to use it.

It's open source and LGPL if you'd like to take a look and/or contribute. :)

2

Yan's statement is inaccurate. Using moveToThread is one way of achieving the proper behavior, but it not the only method.

The alternative is to override the run method and create your objects that are to be owned by the thread there. Next you call exec(). The QThread can have signals, but make sure the connections are all Queued. Also all calls into the Thread object should be through slots that are also connected over a Queued connection. Alternatively function calls (which will run in the callers thread of execution) can trigger signals to objects that are owned by the thread (created in the run method), again the connections need to be Queued.

One thing to note here, is that the constructor and destructor are running in the main thread of execution. Construction and cleanup need to be performed in run. Here is an example of what your run method should look like:

void MythreadDerrivedClass::run()
{
  constructObjectsOnThread();
  exec();
  destructObjectsOnThread();
  m_waitForStopped.wakeAll();
}

Here the constructObjectsOnThread will contain the code one would feel belongs in the constructor. The objects will be deallocated in destructObjectsOnThread. The actual class constructor will call the exit() method, causing the exec() to exit. Typically you will use a wait condition to sit in the destructor till the run has returned.

MythreadDerivedClass::~MythreadDerivedClass()
{
  QMutexLocker locker(&m_stopMutex);
  exit();
  m_waitForStopped.wait(locker.mutex(), 1000);
}

So again, the constructor and destructor are running in the parent thread. The objects owned by the thread must be created in the run() method and destroyed before exiting run. The class destructor should only tell the thread to exit and use a QWaitCondition to wait for the thread to actually finish execution. Note when done this way the QThread derived class does have the Q_OBJECT macro in the header, and does contain signals and slots.

Another option, if you are open to leveraging a KDE library, is KDE's Thread Weaver. It's a more complete task based multitasking implementation similar QtConcurrentRun in that it leverages a thread pool. It should be familiar for anyone from a Qt background.

That said, if you are open to a c++11 method of doing the same thing, I would look at std::async. For one thing, you will no longer have any dependance on Qt, but the api also makes more clear what is going on. With MythreadDerivedClass class inheriting from QThread, the reader gets the impression that MythreadDerivedClass is a thread (since it has an inheritance relationship), and that all its functions run on a thread. However, only the run() method actually runs on a thread. std::async is easier to use correctly, and has fewer gotcha's. All our code is eventually maintained by someone else, and these sorta things matter in the long run.

C++11 /w QT Example:

class MyThreadManager {
  Q_OBJECT
public:
  void sndProgress(int percent)
  void startThread();
  void stopThread();
  void cancel() { m_cancelled = true; }
private:
  void workToDo(); 
  std::atomic<bool> m_cancelled;
  future<void> m_threadFuture;
};

MyThreadedManger::startThread() {
  m_cancelled = false;
  std::async(std::launch::async, std::bind(&MyThreadedManger::workToDo, this));
}

MyThreadedManger::stopThread() {
  m_cancelled = true;
  m_threadfuture.wait_for(std::chrono::seconds(3))); // Wait for 3s
}

MyThreadedManger::workToDo() {
  while(!m_cancelled) {
    ... // doWork
    QMetaInvoke::invokeMethod(this, SIGNAL(sndProgress(int)), 
      Qt::QueuedConnection, percentDone); // send progress
  }
}

Basically, what I've got here isn't that different from how your code would look like with QThread, however, it is more clear that only workToDo() is running on the thread and that MyThreadManager is only managing the thread and not the thread itself. I'm also using MetaInvoke to send a queued signal for sending our progress updates with takes care of the progress reporting requirement. Using MetaInvoke is more explicit and always does the right thing (doesn't matter how you connect signals from your thread managers to other class's slots). You can see that the loop in my thread checks an atomic variable to see when the process is cancelled, so that handles the cancellation requirement.

Atif
  • 1,438
  • 16
  • 25
2

Improve @Hatter answer to support Functor.

#include <QFutureInterfaceBase>
#include <QtConcurrent>

class CancellationToken
{
public:
    CancellationToken(QFutureInterfaceBase* f = NULL) : m_f(f){ }
    bool isCancellationRequested() const { return m_f != NULL && m_f->isCanceled(); }
private:
    QFutureInterfaceBase* m_f;
};

/*== functor task ==*/
template <typename T, typename Functor>
class RunCancelableFunctorTask : public QtConcurrent::RunFunctionTask<T>
{
public:
    RunCancelableFunctorTask(Functor func) : m_func(func) { }
    void runFunctor() override
    {
        CancellationToken token(this);
        this->result = m_func(token);
    }
private:
    Functor m_func;
};

template <typename Functor>
class RunCancelableFunctorTask<void, Functor> : public QtConcurrent::RunFunctionTask<void>
{
public:
    RunCancelableFunctorTask(Functor func) : m_func(func) { }
    void runFunctor() override
    {
        CancellationToken token(this);
        m_func(token);
    }
private:
    Functor m_func;
};

template <class T>
class HasResultType
{
    typedef char Yes;
    typedef void *No;
    template<typename U> static Yes test(int, const typename U::result_type * = 0);
    template<typename U> static No test(double);
public:
    enum { Value = (sizeof(test<T>(0)) == sizeof(Yes)) };
};

class CancelableTaskExecutor
{
public:
    //function<T or void (const CancellationToken& token)>
    template <typename Functor>
    static auto run(Functor functor)
        -> typename std::enable_if<!HasResultType<Functor>::Value,
                        QFuture<decltype(functor(std::declval<const CancellationToken&>()))>>::type
    {
        typedef decltype(functor(std::declval<const CancellationToken&>())) result_type;
        return (new RunCancelableFunctorTask<result_type, Functor>(functor))->start();
    }
};

User example:

#include <QDateTime>
#include <QDebug>
#include <QTimer>
#include <QFuture>
void testDemoTask()
{
    QFuture<void> future = CancelableTaskExecutor::run([](const CancellationToken& token){
        //long time task..
        while(!token.isCancellationRequested())
        {
            qDebug() << QDateTime::currentDateTime();
            QThread::msleep(100);
        }
        qDebug() << "cancel demo task!";
    });
    QTimer::singleShot(500, [=]() mutable { future.cancel(); });
}
Shun
  • 147
  • 11
  • can you please show how one can pause and resume the cancellable task? By the way, this approach has already helped me a lot but now I need to find a way to pause and resume the task. – Shuji Nov 21 '19 at 13:51
  • @ShujaatAliKhan when you need to resume the task, you should hold some variable which is used in the `run Functor`, and then next restart the `Functor` with updated variable. – Shun Nov 23 '19 at 03:25
  • then if you want to get the return value, you should use `QFuture`, T is your result type,and use `QFutureWatcher` to get the finished result. Maybe you need to check the task is finished or canceled. (I'm not sure that `QFutureWatcher::isCanceled()` is going to work.) – Shun Nov 23 '19 at 03:31
1

For a long running single task, QThread is probably your best bet. It doesn't have build-in progress reporting or canceling features so you will have to roll your own. But for simple progress update it's not that hard. To cancel the task, check for a flag that can be set from calling thread in your task's loop.

One thing to note is if you override QThread::run() and put your task there, you can't emit signal from there since the QThread object is not created within the thread it runs in and you can't pull the QObject from the running thread. There is a good writeup on this issue.

Simon1X
  • 101
  • 9
Stephen Chu
  • 12,724
  • 1
  • 35
  • 46
  • actually, the QThread will reside in the main thread as long as you dont call moveToThread(theQthread) and signals can be emitted from the run function until the call to moveToThread is made. Any objects reside in the thread in which they were created that is why you need to call moveTooThread – yan bellavance Mar 29 '11 at 22:44
  • 3
    Thats not true, you can emit back to the main thread using Qt::BlockingQueuedConnection – paulm Jan 10 '13 at 13:49
  • 2
    QThread is unfortunately a bad abstraction. The object itself will live in the parent thread. The run method inside QThread runs on the new thread. Signals in the class are going to be handled by the event loop of the parent/main thread.. this seems to run contrary from what you'd expect, hence the bad abstraction. The most strait forward way of emitting a signal or slot from the QThread object is to use the QMetaInvoke directly. Call it with a Queued connection, and you should get predictable behavior. When in doubt, check the thread id with QThread::currentThreadId() – Atif Jul 20 '15 at 04:47