QFuture, которое можно отменить и сообщить о ходе выполнения
В классе QFuture
есть методы, такие как cancel()
, progressValue()
и т.д. По-видимому, они могут контролироваться с помощью QFutureWatcher
. Однако документация для QtConcurrent::run()
гласит:
Обратите внимание, что QFuture, возвращаемый QtConcurrent:: run() не поддерживает отмена, приостановка или прогресс составление отчетов. Возвращенный QFuture может только для запроса запуск/завершение работы и возврат значение функции.
Я тщетно посмотрел, какой метод действительно может создать QFuture
, который можно отменить и сообщить о прогрессе за одну длительную операцию. (Похоже, может быть, QtConcurrent::map()
и подобные функции могут, но у меня только один, длительный метод.)
(Для тех, кто знаком с .Net, что-то вроде класса BackgroundWorker
.)
Какие опции доступны?
Ответы
Ответ 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(); });
}