QThread documentation suggests two ways to make code run in a separate thread. If I use moveToThread approach, I have to call processEvents() to issue the timeouts, to have the lambda executed. And this seems to cost a lot of CPU. Why is so?
class Worker : public QObject
{
Q_OBJECT
QTimer* timer;
bool m_abort = false;
public:
Worker() {}
void abort() {m_abort = true;}
public slots:
void run() {
timer = new QTimer;
connect(timer, &QTimer::timeout, []{qDebug() << "computed";});
timer->start(1000);
forever {
if (m_abort) break;
QCoreApplication::processEvents();
}
}
};
class MainWidget : public QWidget
{
Q_OBJECT
QThread thread;
Worker* worker;
public:
MainWidget()
{
worker = new Worker;
worker->moveToThread(&thread);
connect(this, &MainWidget::start, worker, &Worker::run);
thread.start();
emit start();
}
~MainWidget(){worker->abort(); thread.quit(); thread.wait();}
signals:
void start();
};
However if I subclass QThread and reimplement run() it's not necessary to call processEvents. And CPU cost seems lower. Why?
class Worker : public QThread
{
public:
Worker() {}
protected:
void run() override {
QTimer timer;
connect(&timer, &QTimer::timeout, []{qDebug() << "computed";});
timer.start(1000);
exec();
}
};
class MainWidget : public QWidget
{
Q_OBJECT
Worker* worker;
public:
MainWidget()
{
worker = new Worker;
worker->start();
}
};
your run() function 'blocks' the thread. It is being invoked in the thread context, but never returns. This means, the event loop in the thread doesn't get executed anymore as soon as your run() funtion is called.
For the timer events to call your lambdas, the event loop has to be processed.
If you would modify your run function like this:
void run() {
timer = new QTimer(this);
connect(timer, &QTimer::timeout, []{qDebug() << "computed";});
timer->start(1000);
// don't loop here, instead exit the function
// and let the thread return back to the event loop
}
then your lambdas should get called. The thread will also keep running until you call thread.quit()
note: you can also connect directly to the '''started''' signal of the thread:
connect(&thread, &QThread::started, worker, &Worker::run);
thread.start();
moveToThread approach might be improved by calling run() function just after thread emit started.
But I still don't know why the way I put it initially doesn't work.
Related
Currently I have two classes that look something like this:
class Worker : public QObject
{
Q_OBJECT
bool aborted = false;
public:
Worker() : QObject() {}
public slots:
void abort() { aborted = true; }
void doWork()
{
while(!aborted && !work_finished)
{
//do work
QCoreApplication::processEvents();
}
}
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() : QObject()
{
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &Worker::deleteLater);
connect(this, &Controller::startWork, worker, &Worker::doWork);
connect(this, &Controller::aborted, worker, &Worker::abort);
}
signals:
void startWork();
void aborted();
};
Controller *cont = new Controller;
emit cont->startWork(); // Start the loop
emit cont->aborted(); // Stop the loop
So the idea is that there is a loop running in a Worker thread, which can be stopped from a Controller thread.
In the example this is done by calling QCoreApplication::processEvents(), which allows signals to call slots before returning control to the loop.
It's important the loop is only stopped at the start or end of an iteration.
Although this works nicely, I think QCoreApplication::processEvents() is pretty expensive, at least when used inside a very long loop (up to thousands in practice).
So my question is, how can I achieve the same result in a better/cheaper way?
There are three alternative solutions that I'm aware of at this time.
1. QThread::requestInterruption (suggested by #Felix)
According to QThread::isInterruptionRequested:
Take care not to call it too often, to keep the overhead low.
Whereas QCoreApplication::processEvents makes no remark on performance or memory usage, so I don't think QThread::requestInterruption is an improvement over QCoreApplication::processEvents in this case.
2. std::atomic (suggested by #Felix)
The main characteristic of atomic objects is that access to this contained value from different threads cannot cause data races [...]
The boolean can be stored inside a std::atomic which can be made a member of the Controller class instead of the Worker class. Then we need to pass a reference to aborted to and store it in Worker, and set it to true from Controller when needed.
I didn't fully test this approach, so please correct me if I got something wrong.
class Worker : public QObject {
Q_OBJECT
std::atomic<bool> &aborted;
public:
Worker(std::atomic<bool> &aborted) : QObject(), aborted(aborted) {}
public slots:
void doWork() {
while(!aborted.load() && !work_finished) /* do work */
}
};
class Controller : public QObject {
Q_OBJECT
QThread workerThread;
std::atomic<bool> aborted;
public:
Controller() : QObject() {
aborted.store(false);
Worker *worker = new Worker(aborted);
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &Worker::deleteLater);
connect(this, &Controller::startWork, worker, &Worker::doWork);
connect(this, &Controller::aborted, worker, &Worker::abort);
}
void abort() { aborted.store(true); }
signals:
void startWork();
};
Controller *cont = new Controller;
emit cont->startWork(); // Start the loop
cont->abort(); // Stop the loop
3. QWaitCondition & QMutex
A boolean paused will be needed. Controller and Worker need read/write access to it.
Set paused to true in Controller when needed.
During the loop in Worker, if(paused): QWaitCondition::wait() until QWaitCondition::wakeAll() is called from the calling thread.
QMutex::lock will need to be called whenever paused is accessed.
class Worker : public QObject {
Q_OBJECT
bool &aborted, &paused;
QWaitCondition &waitCond;
QMutex &mutex;
public:
Worker(bool &aborted, bool &paused, QWaitCondition &waitCond, QQMutex &mutex)
: QObject(), aborted(aborted), paused(paused), waitCond(waitCond), mutex(mutex) {}
public slots:
void doWork() {
while(!aborted && !work_finished) {
//do work
mutex.lock();
if(paused) {
waitCond.wait(&mutex);
paused = false;
}
mutex.unlock();
}
}
void abort() { aborted = true; }
};
class Controller : public QObject {
Q_OBJECT
bool aborted=false, paused=false;
QWaitCondition waitCond;
QMutex mutex;
QThread workerThread;
public:
Controller() : QObject() {
Worker *worker = new Worker(aborted, paused, waitCond, mutex);
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &Worker::deleteLater);
connect(this, &Controller::startWork, worker, &Worker::doWork);
}
void abort() {
mutex.lock();
paused = true; // Worker starts waiting
mutex.unlock();
if(confirmed_by_user) aborted = true; // Don't need to lock because Worker is waiting
waitCond.wakeAll(); // Worker resumes loop
}
signals:
void startWork();
};
Controller *cont = new Controller();
emit cont->startWork(); // Start the loop
cont->abort(); // Stop the loop
I recently started using the QT framework. Yesterday I began programming a simple multithreaded application. At the moment I'm somewhat stuck on the following problem.
Consider two worker classes that both use a thread to do some 'heavy computations'. The first class, FooWorker, looks like the following:
class FooWorker : public QObject
{
Q_OBJECT
public:
FooWorker() : QObject() { }
~FooWorker() { }
signals:
void notify(int);
void aborted();
public slots:
void doWork()
{
int counter = 0;
forever {
// For the sake of this example this reassembles a heavy computational process
if(counter++ < 10) {
emit notify(counter);
QThread::sleep(1);
} else {
counter = 0;
// Wait until we get a signal to restart the process
mutex_.lock();
condition_.wait(&mutex_);
mutex_.unlock();
}
// We should check for a cancellation flag every iteration...
}
emit aborted();
}
private:
QMutex mutex_;
QWaitCondition condition_;
};
The slot 'doWork' will be scheduled to run in another thread. The slot will run forever and is emitting a signal every second until 10 notifications are emitted. After that we wait until it is woken up again.
The second class, BarWorker, looks like this:
class BarWorker : public QObject
{
Q_OBJECT
public:
BarWorker() : QObject() { }
~BarWorker() { }
signals:
void aborted();
public slots:
void doWork()
{
forever {
// Another heavy computational process
QThread::sleep(1);
// We should check for a cancellation flag every iteration...
}
emit aborted();
}
void onNotify(int value)
{
qDebug() << "Notification value:" << value;
}
};
Again the slot 'doWork' will be scheduled to run in another thread. The slot will run forever to do a heavy computational process. Again once the process is done we will wait until it is woken up again (for the sake of this example I left that out in this class).
Finally the main looks like the following:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread* barThread = new QThread();
BarWorker* barWorker = new BarWorker();
barWorker->moveToThread(barThread);
QThread* fooThread = new QThread();
FooWorker* fooWorker = new FooWorker();
fooWorker->moveToThread(fooThread);
// Automatically deletes worker and thread
QObject::connect(fooThread, SIGNAL(started()), fooWorker, SLOT(doWork()));
QObject::connect(fooWorker, SIGNAL(aborted()), fooThread, SLOT(quit()));
QObject::connect(fooWorker, SIGNAL(aborted()), fooWorker, SLOT(deleteLater()));
QObject::connect(fooThread, SIGNAL(finished()), fooThread, SLOT(deleteLater()));
QObject::connect(barThread, SIGNAL(started()), barWorker, SLOT(doWork()));
QObject::connect(barWorker, SIGNAL(aborted()), barThread, SLOT(quit()));
QObject::connect(barWorker, SIGNAL(aborted()), barWorker, SLOT(deleteLater()));
QObject::connect(barThread, SIGNAL(finished()), barThread, SLOT(deleteLater()));
QObject::connect(fooWorker, SIGNAL(notify(int)), barWorker, SLOT(onNotify(int)), Qt::QueuedConnection);
fooThread->start();
barThread->start();
return a.exec();
}
When I run the application nothing gets printed. That was to be expected because the event loop of the BarWorker instance is blocked. As the 'notify' signal gets emitted the 'onNotify' slot is queued onto the event queue. Because we have a never ending loop (until we manually abort it) in the 'doWork' slot, the 'onNotify' slot will not be called. To solve this I can do a couple of things, namely:
Connect the 'notify' signal to the 'onNotify' slot by using the Qt::DirectConnection flag. This way it looks like a normal function call executing on the signalling thread.
Occasionally call the QCoreApplication::processEvents() method to force the event queue to be processed.
Unknown solution I do not know at this time :)???
I hope someone has some alternative solution to this problem, or even suggest an entire different approach, because IMHO above solutions are somewhat ugly and do not feel right.
I don't think there is any "magic" solution to be found here; a thread can't be running Qt's event loop if it is running your own custom event loop. In practice, there are two common solutions, which are really two sides of the same coin:
Call processEvents() periodically from your event loop, as you suggested in your question, so that the Qt event-handling code occasionally gets to run and handle incoming asynchronous signals.
Don't have a long-running loop in your doWork() method. Instead, do a short amount of work, store the results/state of that work in a member variable or somewhere, and then call something like QTimer::singleShot(0, this, SLOT(doWork())) so that the Qt event loop will call your doWork() method again soon after the first call to doWork() returns. That way the Qt event loop never gets held off for longer than the (brief) period of time taken up by a single doWork() call.
Of those two options, I think the second is preferable, because it allows the Qt event loop to run in its normal fashion, and it also avoids a potential tripping-over-your-own-shoelaces issue -- e.g. imagine if while using solution (1) your call to processEvents() causes a slot to be called that deletes the BarWorker object. When the processEvents() call returns, BarWorker::doWork() will resume executing, but at that point, all of the local member variables and virtual methods it might access as part of its normal execution have been destroyed, and reading or writing them will cause undefined behavior (if you're lucky, an easy-to-debug crash). That possible snafu can't happen when using solution (2), since if the BarWorker object gets deleted between calls to doWork(), any queued-up asynchronous call to doWork() will be safely cancelled.
The idiom for a forever loop that interoperates with the event loop is a zero-duration timer. We can factor it out into a WorkerBase class, where the unit of work is to be done in the workUnit method:
// https://github.com/KubaO/stackoverflown/tree/master/questions/worker-timer-40369716
#include <QtCore>
// See http://stackoverflow.com/q/40382820/1329652
template <typename Fun> void safe(QObject * obj, Fun && fun) {
Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread());
if (Q_LIKELY(obj->thread() == QThread::currentThread()))
return fun();
struct Event : public QEvent {
using F = typename std::decay<Fun>::type;
F fun;
Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
Event(const F & fun) : QEvent(QEvent::None), fun(fun) {}
~Event() { fun(); }
};
QCoreApplication::postEvent(
obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun)));
}
class WorkerBase : public QObject {
Q_OBJECT
QBasicTimer timer_;
protected:
virtual void workUnit() = 0;
void timerEvent(QTimerEvent *event) override {
if (event->timerId() == timer_.timerId() && timer_.isActive())
workUnit();
}
public:
using QObject::QObject;
Q_SIGNAL void finished();
/// Thread-safe
Q_SLOT void virtual start() {
safe(this, [=]{
timer_.start(0, this);
});
}
/// Thread-safe
Q_SLOT void virtual stop() {
safe(this, [=]{
if (!isActive()) return;
timer_.stop();
emit finished();
});
}
bool isActive() const { return timer_.isActive(); }
~WorkerBase() {
if (isActive()) emit finished();
}
};
The workers then become:
class FooWorker : public WorkerBase
{
Q_OBJECT
int counter = 0;
bool isDone() const { return counter >= 10; }
void workUnit() override {
if (!isDone()) {
counter ++;
emit notify(counter);
QThread::sleep(1);
} else
stop();
}
public:
void start() override {
counter = 0;
WorkerBase::start();
}
void stop() override {
if (!isDone()) emit aborted();
WorkerBase::stop();
}
Q_SIGNAL void notify(int);
Q_SIGNAL void aborted();
};
class BarWorker : public WorkerBase
{
Q_OBJECT
void workUnit() override {
QThread::sleep(1);
}
public:
void stop() override {
emit aborted();
WorkerBase::stop();
}
Q_SIGNAL void aborted();
Q_SLOT void onNotify(int value)
{
qDebug() << "Notification value:" << value;
}
};
Note that the aborted() and finished() signals have different meanings.
Finally, the test harness:
class Thread : public QThread { public: ~Thread() { quit(); wait(); } };
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
BarWorker barWorker;
FooWorker fooWorker;
Thread barThread, fooThread;
barWorker.moveToThread(&barThread);
fooWorker.moveToThread(&fooThread);
barWorker.start();
fooWorker.start();
QObject::connect(&fooWorker, &FooWorker::finished, &app, &QCoreApplication::quit);
QObject::connect(&fooWorker, &FooWorker::notify, &barWorker, &BarWorker::onNotify);
fooThread.start();
barThread.start();
return app.exec();
}
#include "main.moc"
If you get a QBasicTimer::stop: Failed. Possibly trying to stop from a different thread warning, it's of no consequence and is a result of a Qt bug.
I have a problem with signal/slots in a QThread class. My design looks like this:
class Manager : public QObject {
Q_OBJECT
public:
Manager(QObject* parent) : QObject(parent) {
Thread thread* = new Thread(this);
connect(this, SIGNAL(testsignal()), thread, SLOT(test()));
thread->start();
...
emit testsignal();
}
signals:
void testsignal();
};
class Thread : public QThread {
Q_OBJECT
public slots:
void test() {
qDebug() << "TEST";
}
private:
void run() {}
};
The signal never reaches my test() method. Can someone help? Thanks.
The problem is that sending signals across threads results in queuing the signal into the target thread's event queue (a queued connection). If that thread never processes events, it'll never get the signal.
Also, according to the QThread::run documentation:
Returning from this method will end the execution of the thread.
In other words, having an empty run method results in instant termination of the thread, so you're sending a signal to a dead thread.
Signals sent to a QThread object will go to the thread of the parent object. In this case to the same thread that created it.
To have a object live on another thread you should move it to that thread:
class Manager : public QObject {
Q_OBJECT
public:
Manager(QObject* parent) : QObject(parent) {
Thread thread* = new QThread(this);
Receiver* rec = new Receiver(); //no parent
connect(this, SIGNAL(testsignal()), rec, SLOT(test()));
connect(thread, SIGNAL(finished()), rec, SLOT(deleteLater()));
rec->moveToThread(thread);
thread->start();
...
emit testsignal();
}
signals:
void testsignal();
};
class Receiver: public QObject {
Q_OBJECT
public slots:
void test() {
qDebug() << "TEST";
}
};
I'm working on an application developed with Qt 4.6.
I want to create a custom timer that counts in a separate thread. However, I want this timer to be able to send signals to the main thread.
I subclassed QThread but it doesn't seem to work.
Here is Timer.h:
#ifndef TIMER_H
#define TIMER_H
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QTimer>
class Timer : public QThread
{
Q_OBJECT
public:
explicit Timer(QObject *parent = 0);
~Timer();
// true if the timer is active
bool isCounting();
// start the timer with a number of seconds
void startCounting(int value = 300);
void stopCounting();
// the number of seconds to reach
int maximum();
// the current value of the timer
int value();
// elapsed time since the timer has started
int elapsedTime();
signals:
// sent when the timer finishes to count
void timeout();
// an event is emited at each second when the timer is active
void top(int remainingSeconds);
protected:
// launch the thread
//virtual void run();
private slots:
// decrements the remaining time at each second and emits top()
void timerEvent();
private:
QTimer* _timer;
// remaining time
int _left;
// number of seconds at timer startup
int _maximum;
};
#endif // TIMER_H
And Timer.cpp:
#include "Timer.h"
Timer::Timer(QObject *parent) :
QThread(parent)
{
_timer = new QTimer(this);
_maximum = 0;
_left = 0;
connect(_timer, SIGNAL(timeout()), this, SLOT(timerEvent()));
}
Timer::~Timer()
{
delete _timer;
}
bool Timer::isCounting()
{
// test if timer still active
return _timer->isActive();
}
void Timer::startCounting(int value)
{
qDebug() << QString("Start timer for %1 secs").arg(QString::number(value));
if(_left != 0 || _timer->isActive())
{
_timer->stop();
}
_maximum = value;
_left = value;
// emit the first top
emit top(_left);
// start the timer: 1000 msecs
_timer->start(1000);
// start the thread
start();
}
void Timer::stopCounting()
{
qDebug() << QString("Stopping timer at %1 secs => %2 secs remaining.").arg(QString::number(elapsedTime()), QString::number(_left));
// stop timer
_timer->stop();
_left = 0;
_maximum = 0;
// kill thread
terminate();
}
int Timer::maximum()
{
return _maximum;
}
int Timer::value()
{
return _left;
}
void Timer::timerEvent()
{
qDebug() << "Timer event";
if(--_left == 0)
{
// stop timer
_timer->stop();
// emit end of timer
emit timeout();
// stop thread
terminate();
}
else
{
// emit a signal at each second
emit top(_left);
}
}
int Timer::elapsedTime()
{
return (_maximum - _left);
}
EDIT
I realized the object I tried to move to another thread was actually a singleton. It could lead to a problem (see here).
You don't need to subclass QThread in this particular case. And in general, abstain from subclassing QThread unless you are sure it is what you need.
Here is a quick example how to setup a worker and timer in a thread and launch it:
the worker class:
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = 0) : QObject(parent) {}
signals:
void doSomething();
public slots:
void trigger() {
emit doSomething();
}
};
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MainThreadObject o;
QThread *thread = new QThread;
Worker w;
QTimer timer;
timer.setInterval(1000);
timer.moveToThread(thread);
w.moveToThread(thread);
QObject::connect(thread, SIGNAL(started()), &timer, SLOT(start()));
QObject::connect(&w, SIGNAL(doSomething()), &o, SLOT(doSomething()));
QObject::connect(&timer, SIGNAL(timeout()), &w, SLOT(trigger()));
thread->start();
return a.exec();
}
So, we have the MainThreadObject which represents a QObject derived living in the main thread. We create the timer and Worker object, which is just used to wrap a signal and slot to avoid the need of subclassing QThread. The timer is setup and it and the worker are moved to the new thread, the thread started() signal is connected to the timer start() slot, the worker doSomething() signal is connected to the main thread object doSomething() slot, and finally the timer timeout() signal is connected to the worker trigger() slot. Then the thread is started which initiates the entire chain in the event loop.
As a result, the MainThreadObject::doSomething() is called every second, with the signal emitted from the secondary thread.
Try
QMetaObject::invokeMethod(&timer, "start", Qt::QueuedConnection); //timer->start()
if you want to start timer immediately
Or
QMetaObject::invokeMethod(&timer, "start", Qt::QueuedConnection , Q_ARG(int, 1000 )); //timer->start(200)
if you want to start timer after 1000s
In the Non-GUI thread (QThread or Pthread Callback)
First, if you subclass from QThread, you have to implement run() method, if not, there is no point of doing that, you can inherit from QObject instead.
Second, your QTimer has to reside in a thread that runs an event loop. Without an event loop no Qt queued signals can be transmitted. You can launch an event loop by calling exec() in thread's run method:
void Timer::run() {
exec();
}
Probable reason can be, your timer object is not in a thread with event loop. Event loop is required to trigger the signals.
However, I would suggest that you should not go with this approach. Timers use different mechanism on different platform and your code might not behave as expected on different platform.
I am using Qt in order to write a GUI application.
A main thread is responsible for the GUI and creates an QThread in order to do some work with an object.
class Worker
{
void start() {
QTimer* timer = new Timer();
connect(timer,SIGNAL(timeout()),this,SLOT(do()));
}
void do() {
//do some stuff
emit finished();
}
}
class GUI
{
//do some GUI work then call startWorker();
void startWorker() {
QThread* thread = new Thread();
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(start()));
connect(worker, SIGNAL(finished()), workerthread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
}
}
Now I have several problems:
The timer in my worker class does not work. Maybe it is because the new thread has no event loop, but I have no idea how to create such one. I tried
connect(workerthread, SIGNAL(started()), workerthread, SLOT(exec()));
but it does not work either.
When I try to wait on the new thread, the signal is never sent
class GUI
{
void exit() {
thread->wait();
}
}
I think it also is because there is no event loop and because of that no signal is emitted.
Does anybody have an idea how to solve these problems?
why not use qthreadpool, than you make your task class inherits from qrunnable and qobject, this way you can use signals and slots to pass data from one thread to another, is much simpler to implement, and increase performance from not recreating a thread or having one sleeping all the time
class myTask : public QObject, public QRunnable{
Q_OBJECT
protected:
void run(); //where you actually implement what is supposed to do
signals:
void done(int data);//change int to whatever data type you need
}
//moc click example, or use a timer to call this function every x amount of time
void button_click(){
myTask *task = new myTask();
task->setAutoDelete(true);
connect(task,SIGNAL(done(int)),this,SLOT(after_done(int)),Qt::QueuedConnection);
QThreadPool::globalInstance()->start(task);
}
by default you application gets 1 thread automatically, which you can use to handle the graphic, than use the qthreadpool to process the data/object on demand, you can even set the max amount of threads your application can use to process new request, the others will stay in a queue until one thread is freed
QThreadPool::globalInstance()->setMaxThreadCount(5);
this is a sample code for you :
QThread* thread = new QThread();
Worker* worker = new Worker(3000);
worker->moveToThread(thread);
QObject::connect(thread, SIGNAL(started()), worker, SLOT(start()));
thread->start();`
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(qint32,QObject *parent = 0);
qint32 myTime;
signals:
void workFinished();
public slots:
void doWork();
void start();
private:
QTimer *timer;
};
#include "worker.h"
#include <QTimer>
#include <QDebug>
Worker::Worker(qint32 t,QObject *parent) :
QObject(parent)
{
myTime=t;
}
void Worker::start()
{
timer = new QTimer();
timer->start(myTime);
qDebug()<<QString("start work in time:%1").arg(myTime);
connect(timer,SIGNAL(timeout()),this,SLOT(doWork()));
}
void Worker::doWork()
{
qDebug()<<"dowork";
timer->stop();
emit workFinished();
}
Debug results :
start work in time:3000
I hope this helps you.