1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qthreadpool.h"
41#include "qthreadpool_p.h"
42#include "qdeadlinetimer.h"
43#include "qcoreapplication.h"
44
45#include <algorithm>
46
47QT_BEGIN_NAMESPACE
48
49/*
50 QThread wrapper, provides synchronization against a ThreadPool
51*/
52class QThreadPoolThread : public QThread
53{
54 Q_OBJECT
55public:
56 QThreadPoolThread(QThreadPoolPrivate *manager);
57 void run() override;
58 void registerThreadInactive();
59
60 QWaitCondition runnableReady;
61 QThreadPoolPrivate *manager;
62 QRunnable *runnable;
63};
64
65/*
66 QThreadPool private class.
67*/
68
69
70/*!
71 \internal
72*/
73QThreadPoolThread::QThreadPoolThread(QThreadPoolPrivate *manager)
74 :manager(manager), runnable(nullptr)
75{
76 setStackSize(manager->stackSize);
77}
78
79/*
80 \internal
81*/
82void QThreadPoolThread::run()
83{
84 QMutexLocker locker(&manager->mutex);
85 for(;;) {
86 QRunnable *r = runnable;
87 runnable = nullptr;
88
89 do {
90 if (r) {
91 const bool del = r->autoDelete();
92 Q_ASSERT(!del || r->ref == 1);
93
94
95 // run the task
96 locker.unlock();
97#ifndef QT_NO_EXCEPTIONS
98 try {
99#endif
100 r->run();
101#ifndef QT_NO_EXCEPTIONS
102 } catch (...) {
103 qWarning(msg: "Qt Concurrent has caught an exception thrown from a worker thread.\n"
104 "This is not supported, exceptions thrown in worker threads must be\n"
105 "caught before control returns to Qt Concurrent.");
106 registerThreadInactive();
107 throw;
108 }
109#endif
110
111 if (del)
112 delete r;
113 locker.relock();
114 }
115
116 // if too many threads are active, expire this thread
117 if (manager->tooManyThreadsActive())
118 break;
119
120 if (manager->queue.isEmpty()) {
121 r = nullptr;
122 break;
123 }
124
125 QueuePage *page = manager->queue.first();
126 r = page->pop();
127
128 if (page->isFinished()) {
129 manager->queue.removeFirst();
130 delete page;
131 }
132 } while (true);
133
134 // if too many threads are active, expire this thread
135 bool expired = manager->tooManyThreadsActive();
136 if (!expired) {
137 manager->waitingThreads.enqueue(t: this);
138 registerThreadInactive();
139 // wait for work, exiting after the expiry timeout is reached
140 runnableReady.wait(lockedMutex: locker.mutex(), deadline: QDeadlineTimer(manager->expiryTimeout));
141 ++manager->activeThreads;
142 if (manager->waitingThreads.removeOne(t: this))
143 expired = true;
144 if (!manager->allThreads.contains(value: this)) {
145 registerThreadInactive();
146 break;
147 }
148 }
149 if (expired) {
150 manager->expiredThreads.enqueue(t: this);
151 registerThreadInactive();
152 break;
153 }
154 }
155}
156
157void QThreadPoolThread::registerThreadInactive()
158{
159 if (--manager->activeThreads == 0)
160 manager->noActiveThreads.wakeAll();
161}
162
163
164/*
165 \internal
166*/
167QThreadPoolPrivate:: QThreadPoolPrivate()
168{ }
169
170bool QThreadPoolPrivate::tryStart(QRunnable *task)
171{
172 Q_ASSERT(task != nullptr);
173 if (allThreads.isEmpty()) {
174 // always create at least one thread
175 startThread(runnable: task);
176 return true;
177 }
178
179 // can't do anything if we're over the limit
180 if (activeThreadCount() >= maxThreadCount)
181 return false;
182
183 if (waitingThreads.count() > 0) {
184 // recycle an available thread
185 enqueueTask(task);
186 waitingThreads.takeFirst()->runnableReady.wakeOne();
187 return true;
188 }
189
190 if (!expiredThreads.isEmpty()) {
191 // restart an expired thread
192 QThreadPoolThread *thread = expiredThreads.dequeue();
193 Q_ASSERT(thread->runnable == nullptr);
194
195 ++activeThreads;
196
197 thread->runnable = task;
198
199 // Ensure that the thread has actually finished, otherwise the following
200 // start() has no effect.
201 thread->wait();
202 Q_ASSERT(thread->isFinished());
203 thread->start();
204 return true;
205 }
206
207 // start a new thread
208 startThread(runnable: task);
209 return true;
210}
211
212inline bool comparePriority(int priority, const QueuePage *p)
213{
214 return p->priority() < priority;
215}
216
217void QThreadPoolPrivate::enqueueTask(QRunnable *runnable, int priority)
218{
219 Q_ASSERT(runnable != nullptr);
220 for (QueuePage *page : qAsConst(t&: queue)) {
221 if (page->priority() == priority && !page->isFull()) {
222 page->push(runnable);
223 return;
224 }
225 }
226 auto it = std::upper_bound(first: queue.constBegin(), last: queue.constEnd(), val: priority, comp: comparePriority);
227 queue.insert(i: std::distance(first: queue.constBegin(), last: it), t: new QueuePage(runnable, priority));
228}
229
230int QThreadPoolPrivate::activeThreadCount() const
231{
232 return (allThreads.count()
233 - expiredThreads.count()
234 - waitingThreads.count()
235 + reservedThreads);
236}
237
238void QThreadPoolPrivate::tryToStartMoreThreads()
239{
240 // try to push tasks on the queue to any available threads
241 while (!queue.isEmpty()) {
242 QueuePage *page = queue.first();
243 if (!tryStart(task: page->first()))
244 break;
245
246 page->pop();
247
248 if (page->isFinished()) {
249 queue.removeFirst();
250 delete page;
251 }
252 }
253}
254
255bool QThreadPoolPrivate::tooManyThreadsActive() const
256{
257 const int activeThreadCount = this->activeThreadCount();
258 return activeThreadCount > maxThreadCount && (activeThreadCount - reservedThreads) > 1;
259}
260
261/*!
262 \internal
263*/
264void QThreadPoolPrivate::startThread(QRunnable *runnable)
265{
266 Q_ASSERT(runnable != nullptr);
267 QScopedPointer <QThreadPoolThread> thread(new QThreadPoolThread(this));
268 thread->setObjectName(QLatin1String("Thread (pooled)"));
269 Q_ASSERT(!allThreads.contains(thread.data())); // if this assert hits, we have an ABA problem (deleted threads don't get removed here)
270 allThreads.insert(value: thread.data());
271 ++activeThreads;
272
273 thread->runnable = runnable;
274 thread.take()->start();
275}
276
277/*!
278 \internal
279
280 Helper function only to be called from waitForDone(int)
281*/
282void QThreadPoolPrivate::reset()
283{
284 // move the contents of the set out so that we can iterate without the lock
285 QSet<QThreadPoolThread *> allThreadsCopy;
286 allThreadsCopy.swap(other&: allThreads);
287 expiredThreads.clear();
288 waitingThreads.clear();
289 mutex.unlock();
290
291 for (QThreadPoolThread *thread: qAsConst(t&: allThreadsCopy)) {
292 if (!thread->isFinished()) {
293 thread->runnableReady.wakeAll();
294 thread->wait();
295 }
296 delete thread;
297 }
298
299 mutex.lock();
300}
301
302/*!
303 \internal
304
305 Helper function only to be called from waitForDone(int)
306*/
307bool QThreadPoolPrivate::waitForDone(const QDeadlineTimer &timer)
308{
309 while (!(queue.isEmpty() && activeThreads == 0) && !timer.hasExpired())
310 noActiveThreads.wait(lockedMutex: &mutex, deadline: timer);
311
312 return queue.isEmpty() && activeThreads == 0;
313}
314
315bool QThreadPoolPrivate::waitForDone(int msecs)
316{
317 QMutexLocker locker(&mutex);
318 QDeadlineTimer timer(msecs);
319 do {
320 if (!waitForDone(timer))
321 return false;
322 reset();
323 // More threads can be started during reset(), in that case continue
324 // waiting if we still have time left.
325 } while ((!queue.isEmpty() || activeThreads) && !timer.hasExpired());
326
327 return queue.isEmpty() && activeThreads == 0;
328}
329
330void QThreadPoolPrivate::clear()
331{
332 QMutexLocker locker(&mutex);
333 while (!queue.isEmpty()) {
334 auto *page = queue.takeLast();
335 while (!page->isFinished()) {
336 QRunnable *r = page->pop();
337 if (r && r->autoDelete()) {
338 Q_ASSERT(r->ref == 1);
339 locker.unlock();
340 delete r;
341 locker.relock();
342 }
343 }
344 delete page;
345 }
346}
347
348/*!
349 \since 5.9
350
351 Attempts to remove the specified \a runnable from the queue if it is not yet started.
352 If the runnable had not been started, returns \c true, and ownership of \a runnable
353 is transferred to the caller (even when \c{runnable->autoDelete() == true}).
354 Otherwise returns \c false.
355
356 \note If \c{runnable->autoDelete() == true}, this function may remove the wrong
357 runnable. This is known as the \l{https://en.wikipedia.org/wiki/ABA_problem}{ABA problem}:
358 the original \a runnable may already have executed and has since been deleted.
359 The memory is re-used for another runnable, which then gets removed instead of
360 the intended one. For this reason, we recommend calling this function only for
361 runnables that are not auto-deleting.
362
363 \sa start(), QRunnable::autoDelete()
364*/
365bool QThreadPool::tryTake(QRunnable *runnable)
366{
367 Q_D(QThreadPool);
368
369 if (runnable == nullptr)
370 return false;
371
372 QMutexLocker locker(&d->mutex);
373 for (QueuePage *page : qAsConst(t&: d->queue)) {
374 if (page->tryTake(runnable)) {
375 if (page->isFinished()) {
376 d->queue.removeOne(t: page);
377 delete page;
378 }
379 if (runnable->autoDelete()) {
380 Q_ASSERT(runnable->ref == 1);
381 --runnable->ref; // undo ++ref in start()
382 }
383 return true;
384 }
385 }
386
387 return false;
388}
389
390 /*!
391 \internal
392 Searches for \a runnable in the queue, removes it from the queue and
393 runs it if found. This function does not return until the runnable
394 has completed.
395 */
396void QThreadPoolPrivate::stealAndRunRunnable(QRunnable *runnable)
397{
398 Q_Q(QThreadPool);
399 if (!q->tryTake(runnable))
400 return;
401 const bool del = runnable->autoDelete();
402
403 runnable->run();
404
405 if (del) {
406 Q_ASSERT(runnable->ref == 0); // tryTake already deref'ed
407 delete runnable;
408 }
409}
410
411/*!
412 \class QThreadPool
413 \inmodule QtCore
414 \brief The QThreadPool class manages a collection of QThreads.
415 \since 4.4
416 \threadsafe
417
418 \ingroup thread
419
420 QThreadPool manages and recyles individual QThread objects to help reduce
421 thread creation costs in programs that use threads. Each Qt application
422 has one global QThreadPool object, which can be accessed by calling
423 globalInstance().
424
425 To use one of the QThreadPool threads, subclass QRunnable and implement
426 the run() virtual function. Then create an object of that class and pass
427 it to QThreadPool::start().
428
429 \snippet code/src_corelib_concurrent_qthreadpool.cpp 0
430
431 QThreadPool deletes the QRunnable automatically by default. Use
432 QRunnable::setAutoDelete() to change the auto-deletion flag.
433
434 QThreadPool supports executing the same QRunnable more than once
435 by calling tryStart(this) from within QRunnable::run().
436 If autoDelete is enabled the QRunnable will be deleted when
437 the last thread exits the run function. Calling start()
438 multiple times with the same QRunnable when autoDelete is enabled
439 creates a race condition and is not recommended.
440
441 Threads that are unused for a certain amount of time will expire. The
442 default expiry timeout is 30000 milliseconds (30 seconds). This can be
443 changed using setExpiryTimeout(). Setting a negative expiry timeout
444 disables the expiry mechanism.
445
446 Call maxThreadCount() to query the maximum number of threads to be used.
447 If needed, you can change the limit with setMaxThreadCount(). The default
448 maxThreadCount() is QThread::idealThreadCount(). The activeThreadCount()
449 function returns the number of threads currently doing work.
450
451 The reserveThread() function reserves a thread for external
452 use. Use releaseThread() when your are done with the thread, so
453 that it may be reused. Essentially, these functions temporarily
454 increase or reduce the active thread count and are useful when
455 implementing time-consuming operations that are not visible to the
456 QThreadPool.
457
458 Note that QThreadPool is a low-level class for managing threads, see
459 the Qt Concurrent module for higher level alternatives.
460
461 \sa QRunnable
462*/
463
464/*!
465 Constructs a thread pool with the given \a parent.
466*/
467QThreadPool::QThreadPool(QObject *parent)
468 : QObject(*new QThreadPoolPrivate, parent)
469{ }
470
471/*!
472 Destroys the QThreadPool.
473 This function will block until all runnables have been completed.
474*/
475QThreadPool::~QThreadPool()
476{
477 waitForDone();
478}
479
480/*!
481 Returns the global QThreadPool instance.
482*/
483QThreadPool *QThreadPool::globalInstance()
484{
485 static QPointer<QThreadPool> theInstance;
486 static QBasicMutex theMutex;
487
488 const QMutexLocker locker(&theMutex);
489 if (theInstance.isNull() && !QCoreApplication::closingDown())
490 theInstance = new QThreadPool();
491 return theInstance;
492}
493
494/*!
495 Reserves a thread and uses it to run \a runnable, unless this thread will
496 make the current thread count exceed maxThreadCount(). In that case,
497 \a runnable is added to a run queue instead. The \a priority argument can
498 be used to control the run queue's order of execution.
499
500 Note that the thread pool takes ownership of the \a runnable if
501 \l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c true,
502 and the \a runnable will be deleted automatically by the thread
503 pool after the \l{QRunnable::run()}{runnable->run()} returns. If
504 \l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c false,
505 ownership of \a runnable remains with the caller. Note that
506 changing the auto-deletion on \a runnable after calling this
507 functions results in undefined behavior.
508*/
509void QThreadPool::start(QRunnable *runnable, int priority)
510{
511 if (!runnable)
512 return;
513
514 Q_D(QThreadPool);
515 QMutexLocker locker(&d->mutex);
516 if (runnable->autoDelete()) {
517 Q_ASSERT(runnable->ref == 0);
518 ++runnable->ref;
519 }
520
521 if (!d->tryStart(task: runnable)) {
522 d->enqueueTask(runnable, priority);
523
524 if (!d->waitingThreads.isEmpty())
525 d->waitingThreads.takeFirst()->runnableReady.wakeOne();
526 }
527}
528
529/*!
530 \overload
531 \since 5.15
532
533 Reserves a thread and uses it to run \a functionToRun, unless this thread will
534 make the current thread count exceed maxThreadCount(). In that case,
535 \a functionToRun is added to a run queue instead. The \a priority argument can
536 be used to control the run queue's order of execution.
537*/
538void QThreadPool::start(std::function<void()> functionToRun, int priority)
539{
540 if (!functionToRun)
541 return;
542 start(runnable: QRunnable::create(functionToRun: std::move(functionToRun)), priority);
543}
544
545/*!
546 Attempts to reserve a thread to run \a runnable.
547
548 If no threads are available at the time of calling, then this function
549 does nothing and returns \c false. Otherwise, \a runnable is run immediately
550 using one available thread and this function returns \c true.
551
552 Note that on success the thread pool takes ownership of the \a runnable if
553 \l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c true,
554 and the \a runnable will be deleted automatically by the thread
555 pool after the \l{QRunnable::run()}{runnable->run()} returns. If
556 \l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c false,
557 ownership of \a runnable remains with the caller. Note that
558 changing the auto-deletion on \a runnable after calling this
559 function results in undefined behavior.
560*/
561bool QThreadPool::tryStart(QRunnable *runnable)
562{
563 if (!runnable)
564 return false;
565
566 if (runnable->autoDelete()) {
567 Q_ASSERT(runnable->ref == 0);
568 ++runnable->ref;
569 }
570
571 Q_D(QThreadPool);
572 QMutexLocker locker(&d->mutex);
573 if (d->tryStart(task: runnable))
574 return true;
575
576 // Undo the reference above as we did not start the runnable and
577 // take over ownership.
578 if (runnable->autoDelete()) {
579 --runnable->ref;
580 Q_ASSERT(runnable->ref == 0);
581 }
582 return false;
583}
584
585/*!
586 \overload
587 \since 5.15
588 Attempts to reserve a thread to run \a functionToRun.
589
590 If no threads are available at the time of calling, then this function
591 does nothing and returns \c false. Otherwise, \a functionToRun is run immediately
592 using one available thread and this function returns \c true.
593*/
594bool QThreadPool::tryStart(std::function<void()> functionToRun)
595{
596 if (!functionToRun)
597 return false;
598
599 Q_D(QThreadPool);
600 QMutexLocker locker(&d->mutex);
601 if (!d->allThreads.isEmpty() && d->activeThreadCount() >= d->maxThreadCount)
602 return false;
603
604 QRunnable *runnable = QRunnable::create(functionToRun: std::move(functionToRun));
605 if (d->tryStart(task: runnable))
606 return true;
607 delete runnable;
608 return false;
609}
610
611/*! \property QThreadPool::expiryTimeout
612
613 Threads that are unused for \a expiryTimeout milliseconds are considered
614 to have expired and will exit. Such threads will be restarted as needed.
615 The default \a expiryTimeout is 30000 milliseconds (30 seconds). If
616 \a expiryTimeout is negative, newly created threads will not expire, e.g.,
617 they will not exit until the thread pool is destroyed.
618
619 Note that setting \a expiryTimeout has no effect on already running
620 threads. Only newly created threads will use the new \a expiryTimeout.
621 We recommend setting the \a expiryTimeout immediately after creating the
622 thread pool, but before calling start().
623*/
624
625int QThreadPool::expiryTimeout() const
626{
627 Q_D(const QThreadPool);
628 return d->expiryTimeout;
629}
630
631void QThreadPool::setExpiryTimeout(int expiryTimeout)
632{
633 Q_D(QThreadPool);
634 if (d->expiryTimeout == expiryTimeout)
635 return;
636 d->expiryTimeout = expiryTimeout;
637}
638
639/*! \property QThreadPool::maxThreadCount
640
641 This property represents the maximum number of threads used by the thread
642 pool.
643
644 \note The thread pool will always use at least 1 thread, even if
645 \a maxThreadCount limit is zero or negative.
646
647 The default \a maxThreadCount is QThread::idealThreadCount().
648*/
649
650int QThreadPool::maxThreadCount() const
651{
652 Q_D(const QThreadPool);
653 return d->maxThreadCount;
654}
655
656void QThreadPool::setMaxThreadCount(int maxThreadCount)
657{
658 Q_D(QThreadPool);
659 QMutexLocker locker(&d->mutex);
660
661 if (maxThreadCount == d->maxThreadCount)
662 return;
663
664 d->maxThreadCount = maxThreadCount;
665 d->tryToStartMoreThreads();
666}
667
668/*! \property QThreadPool::activeThreadCount
669
670 This property represents the number of active threads in the thread pool.
671
672 \note It is possible for this function to return a value that is greater
673 than maxThreadCount(). See reserveThread() for more details.
674
675 \sa reserveThread(), releaseThread()
676*/
677
678int QThreadPool::activeThreadCount() const
679{
680 Q_D(const QThreadPool);
681 QMutexLocker locker(&d->mutex);
682 return d->activeThreadCount();
683}
684
685/*!
686 Reserves one thread, disregarding activeThreadCount() and maxThreadCount().
687
688 Once you are done with the thread, call releaseThread() to allow it to be
689 reused.
690
691 \note This function will always increase the number of active threads.
692 This means that by using this function, it is possible for
693 activeThreadCount() to return a value greater than maxThreadCount() .
694
695 \sa releaseThread()
696 */
697void QThreadPool::reserveThread()
698{
699 Q_D(QThreadPool);
700 QMutexLocker locker(&d->mutex);
701 ++d->reservedThreads;
702}
703
704/*! \property QThreadPool::stackSize
705
706 This property contains the stack size for the thread pool worker
707 threads.
708
709 The value of the property is only used when the thread pool creates
710 new threads. Changing it has no effect for already created
711 or running threads.
712
713 The default value is 0, which makes QThread use the operating
714 system default stack size.
715
716 \since 5.10
717*/
718void QThreadPool::setStackSize(uint stackSize)
719{
720 Q_D(QThreadPool);
721 d->stackSize = stackSize;
722}
723
724uint QThreadPool::stackSize() const
725{
726 Q_D(const QThreadPool);
727 return d->stackSize;
728}
729
730/*!
731 Releases a thread previously reserved by a call to reserveThread().
732
733 \note Calling this function without previously reserving a thread
734 temporarily increases maxThreadCount(). This is useful when a
735 thread goes to sleep waiting for more work, allowing other threads
736 to continue. Be sure to call reserveThread() when done waiting, so
737 that the thread pool can correctly maintain the
738 activeThreadCount().
739
740 \sa reserveThread()
741*/
742void QThreadPool::releaseThread()
743{
744 Q_D(QThreadPool);
745 QMutexLocker locker(&d->mutex);
746 --d->reservedThreads;
747 d->tryToStartMoreThreads();
748}
749
750/*!
751 Waits up to \a msecs milliseconds for all threads to exit and removes all
752 threads from the thread pool. Returns \c true if all threads were removed;
753 otherwise it returns \c false. If \a msecs is -1 (the default), the timeout
754 is ignored (waits for the last thread to exit).
755*/
756bool QThreadPool::waitForDone(int msecs)
757{
758 Q_D(QThreadPool);
759 return d->waitForDone(msecs);
760}
761
762/*!
763 \since 5.2
764
765 Removes the runnables that are not yet started from the queue.
766 The runnables for which \l{QRunnable::autoDelete()}{runnable->autoDelete()}
767 returns \c true are deleted.
768
769 \sa start()
770*/
771void QThreadPool::clear()
772{
773 Q_D(QThreadPool);
774 d->clear();
775}
776
777#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
778/*!
779 \internal
780
781 Returns \c true if \a thread is a thread managed by this thread pool.
782*/
783#else
784/*!
785 \since 6.0
786
787 Returns \c true if \a thread is a thread managed by this thread pool.
788*/
789#endif
790bool QThreadPool::contains(const QThread *thread) const
791{
792 Q_D(const QThreadPool);
793 const QThreadPoolThread *poolThread = qobject_cast<const QThreadPoolThread *>(object: thread);
794 if (!poolThread)
795 return false;
796 QMutexLocker locker(&d->mutex);
797 return d->allThreads.contains(value: const_cast<QThreadPoolThread *>(poolThread));
798}
799
800#if QT_DEPRECATED_SINCE(5, 9)
801/*!
802 \since 5.5
803 \obsolete use tryTake() instead, but note the different deletion rules.
804
805 Removes the specified \a runnable from the queue if it is not yet started.
806 The runnables for which \l{QRunnable::autoDelete()}{runnable->autoDelete()}
807 returns \c true are deleted.
808
809 \sa start(), tryTake()
810*/
811void QThreadPool::cancel(QRunnable *runnable)
812{
813 if (tryTake(runnable) && runnable->autoDelete() && !runnable->ref) // tryTake already deref'ed
814 delete runnable;
815}
816#endif
817
818QT_END_NAMESPACE
819
820#include "moc_qthreadpool.cpp"
821#include "qthreadpool.moc"
822

source code of qtbase/src/corelib/thread/qthreadpool.cpp