LabWindows/CVI

cancel
Showing results for 
Search instead for 
Did you mean: 

Unconnected threading

Hello again folks!

I wouldn't believe this is a unique question, but what would be the recommended way of threading the updates to a CVI GUI from a set of digital / analogue inputs.  I've currently got a timer that runs constantly, polling all the digi-ins and switching LEDs on the front panel accordingly.  The rest of the program then takes cues from the front panel as to what's going on with the inputs.  However, do analogue acquisition with this slows the whole program, and trying to run two timers (e.g. one for constant analogue acquisition and another for a analogue input that requires polling at a different rate) does work as they try to run on the same thread.


And whilst I'm here - is there any way of stopping the error-reporting of:
"NON-FATAL RUN-TIME ERROR:   "ADF.c", line 424, col 20, thread id 0x0000042C:   Library function error (return value == -1 [0xffffffff])."
whenever I use a file/directory function that returns a error value - I'm error-trapping the response already!

    strcpy(strDateStamp,strYear);
    strcat(strDateStamp,strMonth);
    strcat(strDateStamp,strDate);
    strcat(strADFdata,strDateStamp);
    strcpy(strADFdata2,strADFdata);
    strcat(strADFdata2,"\\");
   
    step_counter = 1;
    while ( (ret = SetDir(strADFdata2))==0 )
    {
        strcpy(strADFdata2,strADFdata);
        strcat(strADFdata2,"_");
        sprintf(strTmp,"%d",step_counter++);
        strcat(strADFdata2,strTmp);
        strcat(strADFdata2,"\\");
    }
    MakeDir(strADFdata2);


Cheers!

Message Edited by Pythonist on 05-23-2006 09:47 AM

__________________________________________
The world is full of exciting challenges,
brilliantly disguised as insoluble problems.
0 Kudos
Message 1 of 7
(4,117 Views)

Taking the easy question first, look at SetBreakOnLibraryErrors() to control the library error checking at runtime.  There is also a function for SetBreakOnProtectionErrors().  The thread question is an excelent one and will take a little time to work up a decent response.

0 Kudos
Message 2 of 7
(4,103 Views)

There is so much flexibility when designing a program with threads that no one answer is correct and no one answer will fit everyone’s needs.  Each design needs to be tailored to the requirements of the program.  I’m sure with all the different users here we could probably write chapters on thread based design, but here are some general ideas.

 

Multiple Timers

A common solution when handling multiple tasks is to add CVI timers for the different tasks.  As you have seen, if the User Interface task becomes I/O bound or the user interface executes a callback, performance suffers as the Timers and User Interface callbacks block each other.  This is often handled by adding ProcessSystemEvents() but that increases the chances of problems when a function becomes re-entrant.  There is a fairly simple way to get around this as long as the tasks to be performed do not overlap too much.  The Async Timers provided by asynctmr.fp run a callback in a separate thread.  If the data you are collecting can be acquired and displayed in that thread independent of other tasks, you can use an async timer to update the user interface at a pretty high speed without any real impact on the rest of the program.  This even works if you have multiple tasks that must write to the user interface.  There is no problem with this method as long as there is no overlap between the data display tasks and the operator interface thread.  Any tasks/thread can write to the user interface panel as long as they do not write to the same indicators as another task.

 

A variation of this uses async timers to collect the data in a thread, and thread safe variables to allow the main user thread to request data from the worker thread of the timer.  This is particularly useful when a data stream such as a voltage reading must be displayed to the operator in real-time, but the executing program only needs infrequent access to the most recent data.

 

Continued Below

 

Message Edited by mvr on 05-23-2006 10:56 AM

Message 3 of 7
(4,098 Views)

 

Queues

If the user interface must interact with the data, or if some kind of synchronization is needed between different input data streams then a ThreadSafeQueue is a possible solution.  In this design you use a timer or a callback to load the data from different input devices into queues.  For example a serial port can use a serial port callback to place data in a queue, or an async timer can pull data from a GPIB device and place it in a queue. 

The main user interface thread can then pull the data from each queue as needed.  The advantage of this method is that the input devices are always serviced, even if the operator executes a long callback on the main user thread.  The main thread can go back and pick up the data at any point.  The main thread is responsible for getting the data from the queues, formatting it, and displaying the data on the user interface.  The worker threads are only responsible for collecting the data and placing it in a queue.

 

The use of async timers and thread safe queues are very powerful tools to acquire data while keeping the main user interface responsive.  The key is to identify any task that executes periodically or in a loop, that spends much of the time I/O bound waiting for something to occur.  With the high speed CPUs commonly available, almost any repetitive or time consuming data acquisition process is a candidate for a separate thread.  And CVI really can make threaded data acquisition easy to implement.

 

0 Kudos
Message 4 of 7
(4,092 Views)

Just to add to mvr's good advice above. The Timer control callbacks are handled slightly differently to other controls, leading to some possible considerable simplification (and a couple of traps!) in many GUI based designs.

The first important distinction is that the timer callback is not re-entrant. Ever. It is completely safe to add as many ProcessSystemEvents() calls inside a timer callback as you like. In fact it is my normal practice to have a Timer interval set to 0 (to maximise the CPU time spent on my application) and a ProcessSystemEvents() call at the end of my timer callback function (to ensure good GUI responsiveness despite 100% CPU utilisation). Don't worry that this will cause multiple Timer callbacks to be queue'ed up - this cannot happen either, as there is just a single (internal to CVI) slot for a pending Timer operation.

This last feature gives rise to one of the traps - because the timer callbacks are not queue'ed, a new (CVI internal) timer trigger causes any pending operation to be over-written by the new trigger. If you only have one Timer control, this is not a problem, but you may find unexpected effects if you have more than one Timer with more than one callback routine. For example, Timer A (running at 1 second, say) triggers (internally) but because CVI is servicing some other part of your code at the time, the trigger is held in the pending slot until CVI can get round to performing it. If now you have an unrelated trigger from Timer B (running at 100 ms, say) before the Timer A callback has been serviced, the pending slot will be over-written by the Timer B details and the Timer A callback will be lost. Obviously the exact way this interacts with any program will be dependant on how busy it needs to be.

My preferred method is to only ever use a single Timer control, running at the fastest rate I need for the program. In the above example, I would just use the Timer B rate of 100 ms and maintain a static counter within the callback; every 10 ticks I would branch to the bit of code that might otherwise have been the body of a Timer A callback running at 1 second intervals.

The other trap is more obvious - because the Timers are in the same thread as the main program and are not interrupt based you cannot get precisely regular and predictable timing intervals from them. If this is what you need then the Asynchronous Timers in separate threads are the way to go.

JR

0 Kudos
Message 5 of 7
(4,071 Views)
All good information - thanks!

Perhaps I should be more specific, though.

Medical imaging machine.  Main data is acquired proprietry PCI cards and very useful dll.  All remaining control is via USB DAC, controlled as and when certain operations need performing, but all in very linear, functional fashion.  Simultaneously, feedback signals are given by each component in the form of single digital lines in addition to an overall system monitor.  These need constant monitoring in order to confirm the controlled operations can be / are being performed.

I currently use a timer with a tick value of zero, which reads all the digital channels and updates the GUI accordingly, but also produces a set of extern booleans for use in the main program.  This worked fine up until I tried to use a second timer (the timer's end up fighting for priority), or added analogue acquisition to the timer's callback function (slows the main program severely - code below).

The reason for asking the original question was that I now have to run an analogue acquisition completely seperately from both the original timer and the main program.  The idea of queues, though a good one, doesn't work in this situation as all data / responses are need in real-time... I will start playing with Asynchronous timers (where would I find asynctmr.fp?) as that sounds the closest to what I trying to do.

One final question - when you refer to "thread-safe" variables, you are talking about variables that can potentially be altered in seperate threads simultaneously, yes?  Hence the booleans I'm using (write only in one thread, read only in another) are perfectly thread-safe...


float64 samplingFreq = 10;    // arbitrary setting, should probably understand these, but at least they work...
uInt64 NumScans = 2;        // ditto
extern int AI_Flow;

Analogue_init
{
    int tH4_4_to_7;

    DAQmxCreateTask("analogue_1",&tH4_4_to_7);
    DAQmxCreateAIVoltageChan (tH4_4_to_7, "Dev1/ai0,Dev1/ai1,Dev1/ai2,Dev1/ai3", "", DAQmx_Val_Diff, -10, +10, DAQmx_Val_Volts, NULL);
    DAQmxCfgSampClkTiming (tH4_4_to_7, "", samplingFreq, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps, NumScans);

    AI_Flow = tH4_4_to_7;
}

Analogue_read
{
        DAQmxStartTask(AI_Flow);
        DAQmxReadAnalogF64 (AI_Flow, 1, 10.0, DAQmx_Val_GroupByScanNumber, ReadArray, 4, &samples_per_read, NULL);
        DAQmxStopTask(AI_Flow);

       Output = ReadArray;
}

Analogue_clear
{
    DAQmxClearTask(AI_Flow);
}
__________________________________________
The world is full of exciting challenges,
brilliantly disguised as insoluble problems.
0 Kudos
Message 6 of 7
(4,049 Views)

Look in \national instruments\cvi71\toolslib\toolbox\ for asynctmr.fp  The national instruments directory would default to being in program files.  If you have something other than cvi7.1 that part of the directory name would also obviously change.

I have never run into a problem when reading a variable from one thread that is written from another.  However I have never tried this with variables of greater than 32 bits (one data I/O cycle), not what you are doing, but wanted to make that clear for others who read this.    You are correct that Thread Safe variables come into play whenever it is not just a pure read only in one thread, write only in the other situation.

I am not sure what you mean when you say "all data / responses are need in real-time..."   Your specific requirements would again dictate the possible solutions.  Is it the analog data that must be responded too?  Where is the response going too?  A thread safe queue is simply an easy way to safely move data packets from one thread to another.  It doubles as a way to store the data if the receiving end is not able to respond right away.  They also make an excelent FIFO buffer, even when you do not have a multithreaded program.  Thread Safe Queues also have the ability to generate a callback of there own when data is written or read.  A very powerful feature.

But back to you specific problem.  It looks like you would not need a queue for your application.  It sounds like you can acquire the analog data using an async timer, and that all processing and display of that data can be handled in the single thread of the timer.  If that is the case, this should be very easy to implement with an async timer and will definitely solve your issue with multiple timer conflicts in your main user thread. 

 

 

0 Kudos
Message 7 of 7
(4,036 Views)