QFuture可以取消并报告进度

Dav*_*eer 13 qt multithreading qtconcurrent qfuture

QFuture类有方法,例如cancel(),progressValue()等.这些可明显通过被监测QFutureWatcher.但是,QtConcurrent::run()读取文档:

请注意,QtConcurrent :: run()返回的QFuture不支持取消,暂停或进度报告.返回的QFuture只能用于查询运行/完成状态和函数的返回值.

我实际上可以创建一个QFuture可以取消的方法,并报告单个长时间运行的操作的进度,这看起来是徒劳的.(看起来可能QtConcurrent::map()和类似的功能可以,但我只有一个长期运行的方法.)

(对于熟悉.Net的人来说,就像BackgroundWorker班级一样.)

有哪些选择?

Hat*_*ter 19

虽然这个问题已经发布和回答已经有一段时间了但我决定加入解决这个问题的方法,因为它与这里讨论的内容有很大不同,我认为对其他人可能有用.首先,我的方法的动机是,当框架已经有一些成熟的类比时,我通常不喜欢发明自己的API.所以问题是:我们有一个很好的API来控制由QFuture <>表示的后台计算,但是我们没有支持某些操作的对象.好吧,我们来做吧.查看QtConcurrent :: run内部的内容会使事情变得更加清晰:构造一个仿函数,包装到QRunnable中并在全局ThreadPool中运行.

所以我为我的"可控任务"创建了通用接口:

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;
};
Run Code Online (Sandbox Code Playgroud)

然后,按照qtconcurrentrunbase.h中的内容,我创建了q-runnable来运行这类任务(这段代码主要来自qtconcurrentrunbase.h,但略有修改):

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;
};
Run Code Online (Sandbox Code Playgroud)

最后失踪的跑步者类将返回我们可控制的QFututre <> s:

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

};
Run Code Online (Sandbox Code Playgroud)

用户应该子类化ControllableTask,实现后台例程,有时检查传递给TaskControl实例的TaskRtrol实例的方法shouldRun(),然后使用它:

QFututre<int> futureValue = TaskExecutor::run(new SomeControllableTask(inputForThatTask));
Run Code Online (Sandbox Code Playgroud)

然后她可以通过调用futureValue.cancel()来取消它,同时记住取消是优雅而不是立即.


Ste*_*Chu 1

对于长时间运行的单一任务,QThread这可能是您最好的选择。它没有内置的进度报告或取消功能,因此您必须自己动手。但对于简单的进度更新来说并不难。要取消任务,请检查可以通过任务循环中的调用线程设置的标志。

需要注意的一件事是,如果您重写QThread::run()并将任务放在那里,则无法从那里发出信号,因为 QThread 对象不是在它运行的线程中创建的,并且您无法从正在运行的线程中提取 QObject。关于这个问题有一篇很好的文章。

  • 那不是真的,您可以使用 Qt::BlockingQueuedConnection 发送回主线程 (3认同)
  • 不幸的是,QThread 是一个糟糕的抽象。该对象本身将存在于父线程中。QThread 内部的 run 方法在新线程上运行。类中的信号将由父/主线程的事件循环处理。这似乎与您的预期相反,因此是错误的抽象。从 QThread 对象发出信号或槽的最直接方法是直接使用 QMetaInvoke。使用排队连接调用它,您应该获得可预测的行为。如有疑问,请使用 QThread::currentThreadId() 检查线程 ID (2认同)