Add an advisory interruption mechanism to QThread.

To ease interruption of long running tasks, a new method
QThread::setInterruptionRequested() can be called.
The task can check QThread::isInterruptionRequested()
and act upon it by stopping itself.

These methods are designed to replace the use of a global variable
and other hacky ways to stop a task running in another thread.

Change-Id: I17622dd60d2262078210e7e4294ad6c53a6dc179
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Corentin Jabot 2013-01-20 13:24:30 +01:00 committed by The Qt Project
parent 34195aa6cb
commit 5a02d30a78
6 changed files with 107 additions and 1 deletions

View File

@ -146,7 +146,8 @@ void QAdoptedThread::run()
QThreadPrivate::QThreadPrivate(QThreadData *d)
: QObjectPrivate(), running(false), finished(false),
isInFinish(false), exited(false), returnCode(-1),
isInFinish(false), interruptionRequested(false),
exited(false), returnCode(-1),
stackSize(0), priority(QThread::InheritPriority), data(d)
{
#if defined (Q_OS_UNIX)
@ -801,4 +802,61 @@ bool QThread::event(QEvent *event)
}
}
/*!
\since 5.2
Request the interruption of the thread.
That request is advisory and it is up to code running on the thread to decide
if and how it should act upon such request.
This function does not stop any event loop running on the thread and
does not terminate it in any way.
\sa isInterruptionRequested()
*/
void QThread::requestInterruption()
{
Q_D(QThread);
QMutexLocker locker(&d->mutex);
if (!d->running || d->finished || d->isInFinish)
return;
if (this == QCoreApplicationPrivate::theMainThread) {
qWarning("QThread::requestInterruption has no effect on the main thread");
return;
}
d->interruptionRequested = true;
}
/*!
\since 5.2
Return true if the task running on this thread should be stopped.
An interruption can be requested by requestInterruption().
This function can be used to make long running tasks cleanly interruptible.
Never checking or acting on the value returned by this function is safe,
however it is advisable do so regularly in long running functions.
Take care not to call it too often, to keep the overhead low.
\code
void long_task() {
forever {
if ( QThread::currentThread()->isInterruptionRequested() ) {
return;
}
}
}
\endcode
\sa currentThread() requestInterruption()
*/
bool QThread::isInterruptionRequested() const
{
Q_D(const QThread);
QMutexLocker locker(&d->mutex);
if (!d->running || d->finished || d->isInFinish)
return false;
return d->interruptionRequested;
}
QT_END_NAMESPACE

View File

@ -86,6 +86,9 @@ public:
bool isFinished() const;
bool isRunning() const;
void requestInterruption();
bool isInterruptionRequested() const;
void setStackSize(uint stackSize);
uint stackSize() const;

View File

@ -150,6 +150,7 @@ public:
bool running;
bool finished;
bool isInFinish; //when in QThreadPrivate::finish
bool interruptionRequested;
bool exited;
int returnCode;

View File

@ -377,6 +377,7 @@ void QThreadPrivate::finish(void *arg)
d->thread_id = 0;
d->running = false;
d->finished = true;
d->interruptionRequested = false;
d->isInFinish = false;
d->thread_done.wakeAll();
@ -549,6 +550,7 @@ void QThread::start(Priority priority)
d->finished = false;
d->returnCode = 0;
d->exited = false;
d->interruptionRequested = false;
pthread_attr_t attr;
pthread_attr_init(&attr);

View File

@ -377,6 +377,7 @@ void QThreadPrivate::finish(void *arg, bool lockAnyway)
d->running = false;
d->finished = true;
d->isInFinish = false;
d->interruptionRequested = false;
if (!d->waiters) {
CloseHandle(d->handle);
@ -446,6 +447,7 @@ void QThread::start(Priority priority)
d->finished = false;
d->exited = false;
d->returnCode = 0;
d->interruptionRequested = false;
/*
NOTE: we create the thread in the suspended state, set the

View File

@ -106,6 +106,8 @@ private slots:
void customEventDispatcher();
void requestTermination();
#ifndef Q_OS_WINCE
void stressTest();
#endif
@ -1345,5 +1347,43 @@ void tst_QThread::quitLock()
QVERIFY(exitThreadCalled);
}
class StopableJob : public QObject
{
Q_OBJECT
public:
StopableJob (QSemaphore &sem) : sem(sem) {}
QSemaphore &sem;
public Q_SLOTS:
void run() {
sem.release();
while (!thread()->isInterruptionRequested())
QTest::qSleep(10);
sem.release();
Q_EMIT finished();
}
Q_SIGNALS:
void finished();
};
void tst_QThread::requestTermination()
{
QThread thread;
QVERIFY(!thread.isInterruptionRequested());
QSemaphore sem;
StopableJob *j = new StopableJob(sem);
j->moveToThread(&thread);
connect(&thread, &QThread::started, j, &StopableJob::run);
connect(j, &StopableJob::finished, &thread, &QThread::quit, Qt::DirectConnection);
connect(&thread, &QThread::finished, j, &QObject::deleteLater);
thread.start();
QVERIFY(!thread.isInterruptionRequested());
sem.acquire();
QVERIFY(!thread.wait(1000));
thread.requestInterruption();
sem.acquire();
QVERIFY(thread.wait(1000));
QVERIFY(!thread.isInterruptionRequested());
}
QTEST_MAIN(tst_QThread)
#include "tst_qthread.moc"