QFuture, которое можно отменить и сообщить о прогрессе
QFuture
класс имеет такие методы, как cancel()
, progressValue()
и т. д. По-видимому, их можно контролировать с помощью QFutureWatcher
, Тем не менее, документация для QtConcurrent::run()
гласит:
Обратите внимание, что QFuture, возвращаемое QtConcurrent::run(), не поддерживает отмену, приостановку или создание отчетов о ходе выполнения. Возвращаемое QFuture может использоваться только для запроса статуса выполнения / завершения и возвращаемого значения функции.
Я напрасно искал, какой метод на самом деле может создать QFuture
это можно отменить и сообщить о ходе выполнения одной длительной операции. (Похоже, что может быть QtConcurrent::map()
и аналогичные функции могут, но у меня есть только один, длительный метод.)
(Для тех, кто знаком с.Net, что-то вроде BackgroundWorker
учебный класс.)
Какие варианты доступны?
4 ответа
Для долгого одиночного задания, QThread
это, вероятно, ваш лучший выбор. Он не имеет встроенных отчетов о проделанной работе или отмены функций, поэтому вам придется свернуть свои собственные. Но для простого обновления прогресса это не так сложно. Чтобы отменить задачу, проверьте наличие флага, который можно установить из вызывающего потока в цикле вашей задачи.
Стоит отметить, что если вы переопределите QThread::run()
и поставьте свою задачу там, вы не можете испускать сигнал оттуда, так как объект QThread не создается в потоке, в котором он выполняется, и вы не можете извлечь QObject из запущенного потока. Есть хорошая рецензия на этот вопрос.
Хотя прошло много времени с тех пор, как этот вопрос был опубликован и на него дан ответ, я решил добавить свой способ решения этой проблемы, поскольку он довольно сильно отличается от того, что обсуждалось здесь, и я думаю, что он может быть полезен для кого-то еще. Во-первых, мотивация моего подхода заключается в том, что я обычно не люблю изобретать собственные 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;
};
И, наконец, отсутствующий класс бегуна, который вернет нам управляемый QFututre<>s:
class TaskExecutor {
public:
template <class T>
static QFuture<T> run(ControllableTask<T>* task) {
return (new RunControllableTask<T>(task))->start();
}
};
Пользователь должен создать подкласс ControllableTask, реализовать фоновую подпрограмму, которая иногда проверяет метод shouldRun() экземпляра TaskControl, переданный для запуска (TaskControl&), а затем использовать его следующим образом:
QFututre<int> futureValue = TaskExecutor::run(new SomeControllableTask(inputForThatTask));
Затем она может отменить его, вызвав futureValue.cancel(), имея в виду, что отмена является изящной, а не немедленной.
Я решил эту конкретную проблему некоторое время назад, и сделал что-то под названием "Thinker-Qt"... он предоставляет нечто, называемое QPresent
и QPresentWatcher
:
http://hostilefork.com/thinker-qt/
Это все еще довольно альфа, и я намеревался вернуться и возиться с этим (и должен будет сделать это в ближайшее время). На моем сайте есть слайд-колода. Я также задокументировал, как можно изменить Мандельброта, чтобы использовать его.
Это открытый исходный код и LGPL, если вы хотите посмотреть и / или внести свой вклад.:)
Заявление Яна неточно. Использование moveToThread - это один из способов достижения правильного поведения, но это не единственный метод.
Альтернативой является переопределение метода run и создание ваших объектов, которые будут принадлежать этому потоку. Далее вы вызываете exec(). QThread может иметь сигналы, но убедитесь, что все соединения поставлены в очередь. Также все вызовы в объект Thread должны осуществляться через слоты, которые также подключены через соединение с очередями. В качестве альтернативы вызовы функций (которые будут выполняться в потоке выполнения вызывающей стороны) могут инициировать сигналы для объектов, которые принадлежат потоку (созданному в методе выполнения), и снова соединения должны быть поставлены в очередь.
Здесь нужно отметить одну вещь: конструктор и деструктор работают в основном потоке выполнения. Строительство и очистка должны быть выполнены в бегах. Вот пример того, как должен выглядеть ваш метод run:
void MythreadDerrivedClass::run()
{
constructObjectsOnThread();
exec();
destructObjectsOnThread();
m_waitForStopped.wakeAll();
}
Здесь constructObjectsOnThread будет содержать код, который, как вам кажется, принадлежит конструктору. Объекты будут освобождены в destructObjectsOnThread. Фактический конструктор класса вызовет метод exit(), в результате чего exec () завершится. Как правило, вы будете использовать условие ожидания, чтобы сидеть в деструкторе до тех пор, пока цикл не вернется.
MythreadDerivedClass::~MythreadDerivedClass()
{
QMutexLocker locker(&m_stopMutex);
exit();
m_waitForStopped.wait(locker.mutex(), 1000);
}
Итак, опять же, конструктор и деструктор работают в родительском потоке. Объекты, принадлежащие потоку, должны быть созданы в методе run() и уничтожены перед выходом из run. Деструктор класса должен только сообщить потоку о выходе и использовать QWaitCondition, чтобы дождаться, пока поток фактически завершит выполнение. Обратите внимание, что когда это сделано таким образом, у производного класса QThread в заголовке есть макрос Q_OBJECT, и он содержит сигналы и слоты.
Другой вариант, если вы открыты для использования библиотеки KDE, - это KDE Thread Weaver. Это более полная реализация многозадачности, основанная на задачах, аналогичная QtConcurrentRun в том, что она использует пул потоков. Это должно быть знакомо любому человеку из Qt.
Тем не менее, если вы открыты для метода C++11 и делаете то же самое, я бы посмотрел на std::async
, Во-первых, вы больше не будете зависеть от Qt, но API также проясняет, что происходит. С классом MythreadDerivedClass, унаследованным от QThread, у читателя создается впечатление, что MythreadDerivedClass является потоком (поскольку он имеет отношение наследования) и что все его функции выполняются в потоке. Однако толькоrun()
Метод на самом деле работает в потоке. std::async проще в использовании и имеет меньше ошибок. Весь наш код в конечном итоге поддерживается кем-то другим, и подобные вещи имеют значение в долгосрочной перспективе.
C++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 более явное и всегда делает правильно (не имеет значения, как вы подключаете сигналы от ваших потоковых менеджеров к слотам других классов). Вы можете видеть, что цикл в моем потоке проверяет атомарную переменную, чтобы увидеть, когда процесс отменяется, так что обрабатывает требование отмены.
Улучшите ответ @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(); });
}