Подтвердить что ты не робот

QFuture, которое можно отменить и сообщить о ходе выполнения

В классе QFuture есть методы, такие как cancel(), progressValue() и т.д. По-видимому, они могут контролироваться с помощью QFutureWatcher. Однако документация для QtConcurrent::run() гласит:

Обратите внимание, что QFuture, возвращаемый QtConcurrent:: run() не поддерживает отмена, приостановка или прогресс составление отчетов. Возвращенный QFuture может только для запроса запуск/завершение работы и возврат значение функции.

Я тщетно посмотрел, какой метод действительно может создать QFuture, который можно отменить и сообщить о прогрессе за одну длительную операцию. (Похоже, может быть, QtConcurrent::map() и подобные функции могут, но у меня только один, длительный метод.)

(Для тех, кто знаком с .Net, что-то вроде класса BackgroundWorker.)

Какие опции доступны?

4b9b3361

Ответ 1

Для длительной одиночной задачи QThread, вероятно, лучше всего. Он не имеет встроенных отчетов о прогрессе или отмены функций, поэтому вам придется сворачивать самостоятельно. Но для простого обновления прогресса это не так сложно. Чтобы отменить задачу, установите флажок, который можно задать из потока вызовов в цикле задач.

Следует отметить, что если вы переопределяете QThread::run() и ставите свою задачу там, вы не можете испускать сигнал оттуда, так как объект QThread не создается в потоке, в котором он работает, и вы не можете вытащить QObject из бегущая нить. В этой проблеме есть хорошая запись.

Ответ 2

Хотя прошло некоторое время с момента публикации этого вопроса и ответа, я решил добавить свой способ решения этой проблемы, потому что он сильно отличается от того, что обсуждалось здесь, и я думаю, что это может быть полезно кому-то другому. Во-первых, мотивация моего подхода заключается в том, что я обычно не люблю изобретать собственные 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;
};

Затем, после того, что сделано в 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;
};

И, наконец, недостающий класс runner, который вернет нам управляемый QFututre < > s:

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

};

Пользователь должен поднять ControllableTask, реализовать фоновую процедуру, которая иногда проверяет метод executeRun() экземпляра TaskControl для запуска (TaskControl &), а затем использовать его как:

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

Затем она может отменить его, вызвав futureValue.cancel(), имея в виду, что отмена является изящной и не непосредственной.

Ответ 3

Я решил эту конкретную проблему некоторое время назад и сделал что-то под названием "Thinker-Qt"... он предоставляет что-то, называемое QPresent и QPresentWatcher:

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

Это все еще довольно альфа, и я хотел вернуться и поработать с ним (и это нужно будет сделать в ближайшее время). Там слайд-колода и такая на моем сайте. Я также задокументировал, как можно изменить Мандельброта, чтобы использовать его.

Это open source и LGPL, если вы хотите взглянуть и/или внести свой вклад.:)

Ответ 4

Заявление Яна неточно. Использование moveToThread - один из способов достижения правильного поведения, но это не единственный метод.

Альтернативой является переопределение метода run и создание ваших объектов, которые будут принадлежать этому потоку. Затем вы вызываете exec(). QThread может иметь сигналы, но убедитесь, что все подключения находятся в очереди. Кроме того, все вызовы в объект Thread должны проходить через слоты, которые также подключены к соединению с очередью. В качестве альтернативы вызовы функций (которые будут выполняться в потоке выполнения вызывающих) могут инициировать сигналы для объектов, которые принадлежат потоку (созданного в методе run), снова соединения должны быть очереди.

Здесь следует отметить, что конструктор и деструктор выполняются в основном потоке выполнения. Строительство и очистка должны выполняться во время работы. Вот пример того, как выглядит ваш метод выполнения:

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

Здесь конструкторObjectsOnThread будет содержать код, который, по-видимому, принадлежит конструктору. Объекты будут удалены в destructObjectsOnThread. Фактический конструктор класса вызовет метод exit(), заставив exec() выйти. Обычно вы будете использовать условие ожидания, чтобы сидеть в деструкторе до тех пор, пока пробег не вернется.

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

Итак, конструктор и деструктор выполняются в родительском потоке. Объекты, принадлежащие потоку, должны быть созданы в методе run() и уничтожены до выхода. Деструктор класса должен только сообщать потоку о выходе и использовать QWaitCondition, чтобы дождаться завершения потока. Обратите внимание, что при выполнении этого способа класс QThread имеет макрос Q_OBJECT в заголовке и содержит сигналы и слоты.

Другим вариантом, если вы открыты для использования библиотеки KDE, является KDE Thread Weaver. Это более полная реализация многозадачности на основе задач, подобная QtConcurrentRun, в которой используется пул потоков. Он должен быть знаком для любого из фона Qt.

Тем не менее, если вы открыты к методу С++ 11, чтобы сделать то же самое, я бы посмотрел на std::async. Во-первых, у вас больше не будет зависимости от Qt, но api также дает более четкое представление о том, что происходит. С классом MythreadDerivedClass, наследующим от QThread, читатель получает впечатление, что MythreadDerivedClass является потоком (так как он имеет отношение наследования) и что все его функции выполняются в потоке. Тем не менее, только метод run() фактически работает в потоке. std:: async проще в использовании и имеет меньшее количество файлов. Весь наш код в конечном итоге поддерживается кем-то другим, и эти вещи имеют значение в конечном итоге.

С++ 11/w QT Пример:

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
  }
}

В принципе, то, что у меня здесь, не отличается от того, как будет выглядеть ваш код с QThread, тем более ясно, что в потоке работает только workToDo() и что MyThreadManager управляет только потоком а не сама нить. Я также использую MetaInvoke, чтобы отправить сигнал очереди для отправки наших обновлений прогресса, заботясь о требованиях к отчетности о прогрессе. Использование MetaInvoke более явственно и всегда делает правильные вещи (неважно, как вы подключаете сигналы от ваших менеджеров потоков к другим слотам класса). Вы можете видеть, что цикл в моем потоке проверяет атомную переменную, чтобы увидеть, когда процесс отменен, так что обрабатывает требование отмены.

Ответ 5

Улучшение ответа @Hatter для поддержки 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();
    }
};

Пример пользователя:

#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(); });
}