01-27-2006 08:28 AM
I have a new twist on a question that I have seen posted here a few times as to how to best suspend execution while still maintaining good response to various events.
I have a typical main thread that handles the user interface panel and initiates execution of tests. Thread2 handles UUT communication using an asynchronous timer.
During some phases of UUT testing the main thread is made idle using a loop that includes the SDK SleepEx() function and ProcessSystemEvents().
Thread two occasionally gets data packets that need to be displayed by controls on the main panel created in the main thread. The updates come in bursts of 20-100 packets, at around 20 mS between for each packet. The bursts are spaced 10s of seconds apart so we have intense periods of activity followed by long periods of communication silence.
While it may seem that trying to update the screen at rates of 20mS makes little sense, in this case packet data may be persistent for 4 or more packets. With a high refresh rate of 20mS, the changing data is very visible to the operator on the user screen.
It is not a problem to update the value in the controls on the main panel from thread2, but getting them to redraw requires that that the main thread "wake up" and ProcessSystemEvents(); Setting the main idle loop to something like Sleep(20mS) followed by ProcessSystemEvents(), works, but the main idle loop is forced to wake up frequently when most of the time there is nothing to do. We have found that good user input response does not require anything near this fast of a call rate to ProcessSystemEvents(), so we would like to avoid it if necessary. There can be a lot of other activity on this system so minimizing cpu load in any way helps.
My question is, when SleepEx() is made "Alertable" it can be awakened by an Asynchronous Procedure Call (APC), how can I wake the sleeping main thread up when needed from thread 2 and is it practical to do so, or would I be better off having thread2 maintain the display updates of the controls. The second option has it's own drawbacks since this data display area must also be updated by routines in the main thread. If we can wake the thread up from sleep we can use a longer sleep interval and reduce the overhead resources used to maintain the idle itself.
01-27-2006 09:14 AM
It seems that a thread safe queue can be a good approach to your problem without need to put a thread to wake. You can configure a thread safe queue to communicate between the aquisition thread and the main thread and use it so that only data to be displayed are put in the queue by the acquisition thread.
In the main thread you can configure a queue reader callback that is fired by the presence of data in the queue: this way, the callback will never be executed unless when it effectively needs to process data and the UIR update is confined in the main thread leaving the other thread free to handle the test.
01-27-2006 09:51 AM
I had not thought about using a thread safe queue to trigger a callback. I have found TSQs to be a very useful feature of CVI. In this particular case since I can post the data from thread2 directly to the controls in the main thread, I could also use PostDeferredCallToThread() to get the main thread to redraw, but a TSQ is a cleaner and more flexible way to go.
The next question would depend on the internal implemenation of the TSQs (which we may need one of the NI gurus with access to the internal architecture to answer), will the TSQ callback (or a PostDeferredCallToThread call) wake up the main thread or will it be dependant on the main thread completing SleepEX() and calling ProcessSystemEvents()
01-27-2006 03:14 PM
The thread associated with the callback must call ProcessSystemEvents (or RunUserInterface or GetUserEvent) for the callback to actually get called. You can create a simple wait function that processes CVI events while waiting for a specified number of milliseconds. Here is a simple example:
#include <ansi_c.h>
#include <userint.h>
#include <windows.h>
#include <utility.h>
#define READ_BUFFER_SIZE 10
static int quit = 0;
static unsigned int tsq;
int CVICALLBACK ThreadFunction (void *functionData)
{
int buffer[5] = {0};
while (!quit)
{
CmtWriteTSQData(tsq, buffer, sizeof(buffer)/sizeof(buffer[0]), 0, 0);
Sleep(100);
}
return 0;
}
void CVICALLBACK TsqCallback (int queue, unsigned int event, int value, void *cbData)
{
int buffer[READ_BUFFER_SIZE];
CmtReadTSQData(tsq, buffer, READ_BUFFER_SIZE, 0, 0);
puts("In TsqCallback");
}
static void WaitWhileProcessingEvents(int waitTime)
{
// NOTE - GetTickCount wraps every few days!
// So you need to protect for that to be robust.
int startTime = GetTickCount();
do
{
ProcessSystemEvents();
Sleep(0);
} while (GetTickCount() - startTime < waitTime);
}
void main(void)
{
int threadFunctionId;
CmtNewTSQ(1024, sizeof(int), 0, &tsq);
CmtInstallTSQCallback(tsq, EVENT_TSQ_ITEMS_IN_QUEUE, READ_BUFFER_SIZE, TsqCallback, 0,
CmtGetCurrentThreadID(), 0);
CmtScheduleThreadPoolFunction(DEFAULT_THREAD_POOL_HANDLE, ThreadFunction, 0, &threadFunctionId);
while (!KeyHit())
{
WaitWhileProcessingEvents(100);
}
quit = 1;
CmtWaitForThreadPoolFunctionCompletion(DEFAULT_THREAD_POOL_HANDLE,
threadFunctionId, OPT_TP_PROCESS_EVENTS_WHILE_WAITING);
CmtReleaseThreadPoolFunctionID(DEFAULT_THREAD_POOL_HANDLE, threadFunctionId);
CmtDiscardTSQ(tsq);
}
01-27-2006 04:57 PM
01-27-2006 05:55 PM
01-28-2006 02:07 AM
01-30-2006 09:34 AM
Luis and Mohan,
Thanks for pointing me in the direction of WaitForSingleObject() and MsgWaitForMultipleObjects(). Trying to alert the sleeping thread by queuing an APC was not getting me anywhere. In this case an implementation based on MsgWaitForMultipleObjectsEx() does exactly what I needed.
That is a good point about using SetCtrlVal(). That could definitely be useful in some situations. In my particular case I have a large number of controls that are updated at a very fast rate for a short period of time. I have found I can achieve very good screen update performance using SetCtrlAttribute() ATTR_CTRL_VAL and a final call to ProcessDrawEvents().
100mS was my target sleep interval. Using shorter intervals with Sleep() in a loop was not really the right answer for this particular application. As you point out, putting the main thread to sleep for a period longer than 100mS would undoubtedly be a bad idea. But the MsgWaitForMultipleObjects() technique would probably be very useful for making a more dedicated worker thread idle while having better wakeup response than a simple Sleep() and ProcessSystemEvents() loop. In my experimenting with this it looks like time periods in worker threads of 500mS are not an issue.
For others who come across this thread I think Mohans code is an excellent example of how to implement a wait or idle function while still maintain good response to both the user interface and a background communication thread. If it where not for the odd response and timing requirements of my application I am sure this is the type of implementation I would have used.
01-30-2006 09:38 AM
Roberto,
My case is somewhat unique in that I must release as much of the system resources as possible back to the CPU so that another (less efficient) application can meet its own performance requirements. The unique situation here is that my application must remain idle at a very low cpu load rate for indefinite periods which can be short or long. This is combined with a requirement for a very short wakeup response period. Upon being awakened it needed to update the display at an unusually fast refresh rate. This is not a requirements combination I have come across that often so I suspect your normal implementation is more than adequate. I have made a thread idle before, but never needed to wake up in such a short interval or repeat the operation in such rapid succession
I really do appreciate the input from yourself, Luis and Mohan on this. This is the kind of discussion that really helps to expand my understanding of the details that make specific functions more suitable to a task than others and greatly increases my knowledge of how to interface the SDK with CVI.
01-30-2006 10:27 AM
Ah, so now I understand what's the basic reason for such a structure. You're right that I never had such an application before, but it's always good to know some else's problems and possible solutions since you never know what you will need to do tomorrow.
And you're right: this kind of discussion is not valuable in how helpful can be in deep into programming special applications. I have found several suggestions to face my problems without even needing to post a question!
Good luck with your work
Roberto