我在Qt中有一些I / O代码被移动到QRunnable
以避免阻塞UI线程。这将回调一些排队的插槽以指示其进度。
但是我最近看到了一些问题,当QRunnable
的所有者被销毁时,它无法删除runnable直到它完成(否则在工作线程仍在使用它时崩溃)。为了解决这个问题,我想在运行中添加一个“WaitForFinished
”,它将在dtor中调用它,这样我就可以删除QRunnable
并知道它会阻塞直到它在dtor之前完成退出。
我认为这应该很简单,因为我需要做的就是在QMutex
实现中使用QRunnable::run
,然后在WaitForFinished
中获取该互斥量。然而,这有一个竞争条件,我不知道如何解决。
请考虑以下事项:
QRunnable
排队等待执行。QRunnable
开始执行,但尚未调用QRunnable::run
QRunnable
的{{1}}并在WaitForFinished
之前获取QMutex
- 因此我们假设它未运行或已完成运行并销毁QRunnable::run
,但它还没有开始!答案 0 :(得分:1)
构建runnable时应获取互斥锁,并在run()
清空时释放互斥锁。这必须包括run()
根本不返回时 - 抛出的异常不返回!在两种情况下,您不能随意删除runnable:
一旦提交给线程池,就在它完成之前,
如果设置了autoDelete。
QRunnable::run()
在线程池的实现中返回后,如果autoDelete
的缓存值为真,则runnable中的内部引用计数器将递减。这会导致悬空指针取消引用。
以下是这个想法的经过测试的实现。
输出结果为:
~Incrementer
1
~Incrementer
2
#include <QThreadPool>
#include <QRunnable>
#include <QMutex>
#include <QDebug>
// This cannot be a class that can be derived from, since the destructor
// will be run *after* the derived class's destructor - thus it would not
// protect the derived class's members from premature destruction.
class RunnableWrapper Q_DECL_FINAL : public QRunnable {
Q_DISABLE_COPY(RunnableWrapper)
class LockerUnlocker {
Q_DISABLE_COPY(LockerUnlocker)
QMutexLocker * m_mutexLocker;
public:
explicit LockerUnlocker(QMutexLocker * m) : m_mutexLocker(m) {}
~LockerUnlocker() { m_mutexLocker->unlock(); }
};
QRunnable * m_wrapped;
QMutex m_mutex;
QMutexLocker m_lock;
void run() Q_DECL_FINAL {
LockerUnlocker unlocker(&m_lock);
m_wrapped->run();
}
public:
RunnableWrapper(QRunnable * r) : m_wrapped(r), m_lock(&m_mutex) {
setAutoDelete(false);
}
void wait() { QMutexLocker lock(&m_mutex); }
~RunnableWrapper() {
wait();
if (m_wrapped->autoDelete()) delete m_wrapped;
}
};
class Incrementer : public QRunnable {
int * m_val;
void run() Q_DECL_OVERRIDE { ++ *m_val; }
public:
explicit Incrementer(int * val) : m_val(val) {}
~Incrementer() { qDebug() << __FUNCTION__; }
};
int main() {
QThreadPool pool;
int i = 0;
{
// Use with stack allocated runnable
Incrementer inc(&i);
inc.setAutoDelete(false); // Required without a wrapper as well!
RunnableWrapper wrap(&inc);
pool.start(&wrap);
}
qDebug() << i;
{
// Use with heap allocated runnable
Incrementer * inc = new Incrementer(&i);
RunnableWrapper wrap(inc);
pool.start(&wrap);
}
qDebug() << i;
return 0;
}
答案 1 :(得分:1)
首先,没有一种尺寸适合所有解决方案。首选方法取决于任务细节。虽然我已经提出了一些问题,但仍然存在很多模棱两可的问题。但是,我将描述我能想到的最简单的方法。
startedJobsCounter
并在每次在所有者对象中启动新作业时增加它。 finishedJobsSem
,当作业完成时,该作业将被释放。 所有Runnables的waitForFinished
看起来像
finishedJobsSem.aquire(startedJobsCounter);
您可以在runnable析构函数中释放finishedJobsSem
。
startedJobsCounter
读写之间没有竞争,因为它们都是在所有者的对象线程中执行的。
由于所有作业都将在所有者对象被销毁之前完成,因此使用form runnable无法使finishedJobsSem
无效。
答案 2 :(得分:1)
如果其他人对runnables有这个问题,这就是我最终解决它的方式。我的用例基本上是我想在另一个线程中执行对象的“run”方法,并知道它何时完成/能够等待它。
我发现QtConcurrent
实际上比QRunnable
更好。
class MyClass
{
public:
void run() { }
};
void someFunc()
{
MyClass instance;
// Async execute "run" on "instance", returns a future
auto future = QtConcurrent::run( std::bind(&MyClass::run,instance) );
// Will emit "finished" signal when done - should keep this and the future in scope of course!
QFutureWatcher watcher;
watcher.setFuture( future );
// Blocks until MyClass::run() returns
future.waitForFinished();
}