PDA

View Full Version : QThreads and Windows Multimedia Timer



Cruz
26th June 2011, 20:34
Hello!

I have a GUI and a worker thread. The worker thread is ticked periodically by a windows multimedia timer. I'm not using a QTimer because I need a higher precision. Now the problem is that I want to transfer data from the GUI to the worker thread in a thread safe manner using signals and slots. But the multimedia timer creates its own thread, so the worker loop is in a different thread than the event loop, where the slots are handled. I don't know how to solve this, please help. Here is the code:



/////////////// The GUI class///////////////////
class GUI
{
WorkerThread wt;
Data data;

signals:
dataOut(Data);
}

GUI::GUI()
{
connect(this, SIGNAL(dataOut(Data)), &wt, SLOT(dataIn(Data)));
wt.start();
}

GUI::somewhere()
{
emit dataOut(data);
qDebug() << QThread::currentThreadId() << "data emitted";
}


/////////////// The Worker Thread ///////////////////
class WorkerThread : QThread
{
Data data;
void tick();

public slots:
dataIn(Data);
}

WorkerThread::WorkerThread()
{
moveToThread(this);
}

// This is the callback function of the windows media timer.
void CALLBACK PeriodicCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
WorkerThread* wt = (WorkerThread*)dwUser;
wt->tick();
}

// When the thread is started, I create the MM timer and start the event loop.
void RobotControl::run()
{
// Set resolution to the minimum supported by the system.
TIMECAPS tc;
timeGetDevCaps(&tc, sizeof(TIMECAPS));
timerRes = qMin(qMax(tc.wPeriodMin, (UINT) 0), tc.wPeriodMax);
timeBeginPeriod(timerRes);

// Create the callback timer.
timerId = timeSetEvent(12, 0, PeriodicCallback, (DWORD_PTR) this, 1);

// Start the event loop.
exec();
}

WorkerThread::tick()
{
use(data);
qDebug() << QThread::currentThreadId() << "worker thread was ticked";
}

WorkerThread::dataIn(Data d)
{
data = d;
qDebug() << QThread::currentThreadId() << "data received";
}



Now the output of this code is:


0xaa8 worker thread was ticked
0xaa8 worker thread was ticked
0xaa8 worker thread was ticked
0xb98 data emitted
0x16e4 data received
0xaa8 worker thread was ticked
0xaa8 worker thread was ticked
0xaa8 worker thread was ticked


As you can see, there are three different threads. How can I achieve that the tick()s are executed on the same thread as the data slots?

Santosh Reddy
27th June 2011, 02:16
How can I achieve that the tick()s are executed on the same thread as the data slots?
If you want to execute tick() in the same thread as dataIn() slot, then you mush use event loop, it is one of the event loop's feature to execute timer events (QTimer events) in the threads own context. But as you use separate thread for timer tick() it will not be possible to run tick() and dataIn() slot in same thread. As an alternate you can always share data between the tick() thread and dataIn() thread using a QMutex, something like this.


class WorkerThread : QThread
{
Data data;
QMutex dataMutex;
void tick();

public slots:
dataIn(Data);
}

WorkerThread::tick()
{
dataMutex.lock();
use(data);
dataMutex.unLock();
qDebug() << QThread::currentThreadId() << "worker thread was ticked";
}

WorkerThread::dataIn(Data d)
{
dataMutex.lock();
data = d;
dataMutex.unLock();
qDebug() << QThread::currentThreadId() << "data received";
}

As you are using a native OS timer mechanism for precision reasons, it will better to use native mutex locking and unlocking mechanisms to maintain the precision, using QMutex might add some overhead.

Cruz
27th June 2011, 11:07
I'm sorry but a mutex is not an option, as it delays the timing of my worker thread. What I need is the timer and the dataIn() slot to run on the same thread.

Santosh Reddy
27th June 2011, 11:18
It is not possible, unless you use either event loop, or some kind of resource locking:(

Cruz
27th June 2011, 12:00
Event loop yes, there is already one in my original example. It's just not on the same thread as the timer. Can I somehow move my worker thread to the timer thread so that the event loop is processed there? Or can I somehow process the event loop in the tick() method? I tried to call QApplication::processEvents() there, but it didn't work.

Santosh Reddy
27th June 2011, 12:16
You can write you own event loop in the timer thread, using QEventLoop

Cruz
24th July 2011, 21:45
How can I implement a custom QEventLoop in my multimedia timer driven thread? I have tried creating my own QEventLoop and calling processEvents() on it with every iteration of the timer. But it doesn't do anything. Well, if I don't use the moveToThread() call in the code below, then the dataIn() connection is a direct one and the signal and the slot are executed on the same thread. If I do the moveToThread(), then the connection should be a queued one, but I guess it goes to the wrong thread. How can I tell Qt to handle the slot with the provessEvents() call of my own QEventLoop?





class WorkerThread : QThread
{
QEventLoop* eventLoop; // my custom event loop
Data data;
void tick();

public slots:
dataIn(Data);
}

WorkerThread::WorkerThread()
{
moveToThread(this); // optional
}

// This is the callback function of the windows media timer.
// It just calls tick() of my worker thread.
void CALLBACK PeriodicCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
WorkerThread* wt = (WorkerThread*)dwUser;
wt->tick();
}

// When the thread is started, I create the MM timer and have it call PeriodicCallback().
void RobotControl::start()
{
// Set resolution to the minimum supported by the system.
TIMECAPS tc;
timeGetDevCaps(&tc, sizeof(TIMECAPS));
timerRes = qMin(qMax(tc.wPeriodMin, (UINT) 0), tc.wPeriodMax);
timeBeginPeriod(timerRes);

// Create the callback timer.
timerId = timeSetEvent(12, 0, PeriodicCallback, (DWORD_PTR) this, 1);
}

WorkerThread::tick()
{
// Initialize and call the event loop.
static bool eventLoopInitialized = false;
if (!eventLoopInitialized)
{
eventLoopInitialized = true;
eventLoop = new QEventLoop();
}
else
{
//QApplication::processEvents();
eventLoop->processEvents(); // not sure which one to call
}

use(data);
qDebug() << QThread::currentThreadId() << "worker thread was ticked";
}

// I would like this slot to be handled on the same thread as tick().
WorkerThread::dataIn(Data d)
{
data = d;
qDebug() << QThread::currentThreadId() << "data received";
}

Cruz
25th July 2011, 16:29
Is this question too hard or too stupid?

Santosh Reddy
26th July 2011, 03:33
You can write you own event loop in the timer thread, using QEventLoop
I might have misguided you with this reply, sorry for that. Looks like QEventLoop will work only with QThread (or with Qt Created threads), not with native threads. (AFAIK)

As I see, as said earlier, you need to have some kind to native thread synchronization mechanism, Qt communication may not work with native threads. Even if there were a way to synchronize, I am sure you should compromise on the timing, to some extent.

As I don't know your application requirements, I cannot suggest any workarounds either.


I'm sorry but a mutex is not an option, as it delays the timing of my worker thread. What I need is the timer and the dataIn() slot to run on the same thread.
If mutex is not an option, then signals & slots also are not an option, as signals & slots internally use thread blocking using mutex a bunch of times.

Cruz
26th July 2011, 08:33
Alright. Thank you for the insight, I will see how I can make it work with mutexing.

However, your reply makes me wonder about the justification for the existence of QEventLoop. Since QThreads already come with their own event loop and QEventLoop only works with QThread, what's the use case of QEventLoop?

Santosh Reddy
26th July 2011, 21:21
QEventLoop can be used in a thread, when you want to implement a custom exec() like funtionality, you can at any time during the thread execution create a QEventLoop and process the required events locally, and then then continue with your job. I guess there is no way to exit the inbuilt eventloop from with in the same thread, in such cases QEventLoop may be handy.