LabWindows/CVI

cancel
Showing results for 
Search instead for 
Did you mean: 

timers stop when moving panel

Solved!
Go to solution

I'm realtively new to LabWindows, and up until now this hasnt been a problem, but now I'm writing more time critical applications, it is. Put simply I need to find a way round the problem of timers callbacks stopping whilst manipulating panels - moving etc. My thoughts were if I could ecapsulate the timer callbacks in their own thread this might solve this problem, but am not sure if this is the easiest solution or how really to go about implementing it. 

 

If I take the example of a simple stopwatch. I'm wouldnt be concerned about whether the stopwatch gui continues to update, if the panel is being manipulated - just that when it is released, the stopwatch records the correct time and doesnt just tick on from the point where it was frozen.

 

Any simple examples that you could given me to get a feel of how to get round this problem greatly received

 

Regards


Gavin 

0 Kudos
Message 1 of 10
(5,178 Views)

Grabbing and moving a Window uses a bunch of CPU time which you can verify by firing up task manager, then grabbing its window and swishing it around while viewing the performance tab.  Everything else can come to a stop when you do this, including callbacks from timers.  Behind the scenes, the main window of your app is getting lots of repaint messages from the OS, which the CVI runtime has to respond to, so your timer callback comes out on the short end of things.

 

Async timers run on their own thread (all of the async timers in your app run on one specific thread) and you may see that this thread gets scheduled more reliably than the main thread that the regular timer controls run on and I believe it's that same main thread that has to read the message queue and repaint the window when told to by the OS and when you process events in your app.

 

So I'd try using async timers instead.  Threads are scheduled independently of the process they are in:  all threads of all processes on the system are considered equally for scheduling pruposes, subject only to priority.  Note that the priorities of the threads within a given process are related however, by  the concept of process priority (which means that the thread priorities within the proces all are biased up or down from the process's base priority).

 

CVI controls the priority of the async timer thread, you never get a handle to it and I don't think you can adjust its priority in any direct way.  But, it's not going to be busy repainting the GUI so it should run more reliably.

 

You might try setting the outline dragging only feature of the main panel and see if that offloads some of the CPU time consumed repainting the window.  I don't see how to do this in CVI 2009 but it used to be an option I thought.  You could make the panel non-movable, non re sizeable then the user couldn't induce the problem.

 

 

Menchar

 

Message 2 of 10
(5,170 Views)

User interface controls, like timers are, are subject to the ability of the processor to handle events. Under some circumstances, this is not possible and timer controls do not fire events: dragging a top-level panel or simply keeping the mouse pressed on the panel title stop event handling.

A way to prevent this event is to split the application into independent threads, so that an operator event like dragging a panel with the mouse is kept  independent from the other threads life.

 

Now, since you are programming with a timer, the easiest way to split the application into threads is to replace the UI timer with an asynchronous timer: add toolslib\toolbox\asynctmr.fp to the project and use NewAsyncTimer to create the timer, assigning the callback of the UI timer to it (async timer callback has been designed to have the same function prototype as standard UI timers) and that's all.

Look at samples\toolbox\asyncdem for an example of using async timers.

 

Two additional hints when using async timers:

- 'panel' parameter is reserved in the timer callback, so when using the async timer you cannot use it to identificate an actual panel

- in a multithreaded environment special precautions are to be taken to prevent concurrent access to variables by two threads: <cvidir>\bin\MultithreadingOverview.pdf document describes some techniques that can be used in this case to prevent such problems



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
Message 3 of 10
(5,168 Views)
Solution
Accepted by topic author umquat

Thats great... Thanks. I'd been looking at that as an option. I'll have a play around and see if I can get it working. Thanks for the tips btw... I dont think theres anything I'm doing that is likely to create any problems which is likely to create any file access issues or potential deadlocks, but I'll bear them in mind.

 

Keep you informed of progress and might well be back for more advice.

 

 

Regards

 

Gavin

0 Kudos
Message 4 of 10
(5,150 Views)

Thanks guys... I've switch over a couple of timers to the asynchronus equivalents and the problems are solved. Was rather easier than I'd convinced myself it was going to be. As it turned out there were some threadsafevar issue, but they were fairly easy to resolve as well.

 

Cheers for the help Smiley Wink

 

Regards


Gavin

Message Edited by umquat on 03-15-2010 06:07 AM
0 Kudos
Message 5 of 10
(5,121 Views)
Contrary perhaps to what has been written above, I think missing timer callbacks are not a 'lack of CPU' issue; they are explicitly disabled by CVI during panel moves, resizes, etc. They can be enabled by setting the system attribute ATTR_ALLOW_UNSAFE_TIMER_EVENTS to 1, but best to read the help page on this attribute before doing it Smiley Happy.
--
Martin
Certified CVI Developer
Message 6 of 10
(5,113 Views)

Okay, so I now have a system which for a while at least is is doing exactly what I need it to do... Let me explain.

 

I have a number of asynchronous timers running continuously. Timer 1 is running at intervals as low as 0.1 seconds. This is being used for data capture from an external USB device and storage in an array of 4MB size. This storage will eventually be dynamically allocated. Timer 2 is used to write this data to a csv data file every 2 seconds although there doesnt seem to be much overhead when writing out at the same speed as data capture takes place. The write only takes place if Timer 1 has ticked. Timer 3 clocks at 1 second intervals and is used as a stopwatch to sequence a digital output high or low, which in turn causes the hardware to change the analog input that Timer 1 is reading. Now up until this point we're okay. The PC seems to cope perfectly well with this setup. However when we add Timer 4 things start to get a bit clunky.

 

Timer 4 is used to update a near time graph every 2 seconds, with the data recorded by Timer 1 and the state of the output sequenced by Timer 3. This graph has to be continuous. It doesnt plot every point, but records at a minimum of 1 second intervals. If we take the scenario where we are capturing at 0.1 second intervals, then the graph updating - using the PlotLine function, and with the graph in Retain Data mode, things are okay for around 30 minutes. After that however it starts to loose data points as the graph draws and rescales itself. Given that this program might need to run for several hours continuously this is problem. It isnt critical that absolutely every data point is captured, but the fact that after maybe a couple of hours of use, the drop out is such that every time the graph updates 5 or 6 data points are lost is a problem.

 

I tried a run last night, this time recording data a 0.5 second intervals. Although with this setup there was more stability for the first 3-4 hours of use, after a 10 hour run things were really clunky, and I had lost on average something like 2% of all the data points that should've been recorded.

 

So I guess the question is what can I do. Essentially I need to make the actions associated with the graph update / redraw and low a priority as possible and the data capture as high as possible, but as my knowledge of threading is still very limited I'm note quite sure how to go about it.

 

Here is the snippet of code that I've described up until now. You will notice that I've attempted with my basic understanding to add the graph redraw in its own thread (from what I've been able to work out although I could be completely wrong in my assumption), but the effects of this were negligible over the periods of time I've been talking about. 

 

See next post
0 Kudos
Message 7 of 10
(4,953 Views)

int CVICALLBACK MainPanelAsyncTimerProc (int iReserved, int control, int event, void *callbackData, int eventData1, int eventData2)
{
    static BOOL bDataTimerTicked = FALSE, bLogTimerTicked = FALSE;
    double *dProcPtr;
    int status = 0, i;
    BOOL bRet;
    char szErrorMsg[CMT_MAX_MESSAGE_BUF_SIZE];
   
    switch (event)
    {
        case EVENT_TIMER_TICK:
       
           
            if (control == DataTimerID)                         //Data
            {
                dProcPtr = GetPtrToProcDataPtr();
                if (ReadAndProcessData(dProcPtr, dLogStartTime, dM, dC) == FALSE)
                {
                    ReleasePtrToProcDataPtr(dProcPtr);
                    return -1;
                }
                else
                {
                    bDataTimerTicked = TRUE;        //Set if the ReadData request succeeded
                }
                UpdateMainPanelPressureDisplay(hMainPanel, iPressureUnitsSelected, dProcPtr);        //uses ProcData to update screen
                if (bPlotting == TRUE)
                {
                    UpdateRealTimeGraph(FALSE, hGraphPanel, dProcPtr, &chans);            //uses ProcData to update plots, if enabled
                }
                ReleasePtrToProcDataPtr(dProcPtr);
            }
            else if (control == LogTimerID)
            {
                if (bDataLogging && bDataTimerTicked)      //Check to see if logging and also if the data timer ticked since the last log request
                {
                    dProcPtr = GetPtrToProcDataPtr();
                    DataLogWrite(hMainPanel, dProcPtr, &i);       //Write out to the data file
                    bRet = StoreDataInBuffer(dProcPtr,i);    //and store in buffer for use in real time logging
                    if (bRet == FALSE)                         //Filled available buffer space Stop logging
                    {
                        DataLogStop(hMainPanel,hMainMenuBar,LogTimerID,RealTimeTimerID, TRUE);
                        bDataLogging = FALSE;
                        SetDataLoggingStatus(bDataLogging);
                    }
                    ReleasePtrToProcDataPtr(dProcPtr);
                    bDataTimerTicked = FALSE;
                    bLogTimerTicked = TRUE;
                }
            }
            else if (control == PulseTimerID)
            {
                if (bDataLogging)
                {
                    dProcPtr = GetPtrToProcDataPtr();
                    ExecutePulseCommand(FALSE,dProcPtr, NULL, 0, iPressureUnitsSelected, FALSE, PulseTimerID);  //Second FALSE is an ignored state here
                    ReleasePtrToProcDataPtr(dProcPtr);
                }
            }
            else if (control == RealTimeTimerID)
            {
                if (bDataLogging && bLogTimerTicked)
                {
                    if (CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, DisplayDataFromDataArray, NULL, NULL) == 1)
                    {   
                        DataLogStop(hMainPanel,hMainMenuBar,LogTimerID,RealTimeTimerID, TRUE);
                        bDataLogging = FALSE;
                        SetDataLoggingStatus(bDataLogging);
                    }
                }
                bLogTimerTicked = FALSE;
                SetDataLoggingStatus(bDataLogging); 
            }
    }
           
    return 0;
}

 

 

 

 

 

Timer 1 is DataTimerID

 

Timer 2 is LogTimerID

 

Timer 3 is PulseTimerID

 

Timer 4 is RealTimeTimerID

0 Kudos
Message 8 of 10
(4,952 Views)

Without deeping into into the timer callback, as far as I can see the problem seems related to graph autoscaling during process. Since the problem is already visible in relatively small periods of time, I will try two things before thinking to rewrite all the process:

 

1. using a graph without autoscaling: if you know how long will be your process, you can accomodate the Xaxis scale of the graph so that all data can fit in it without need for scaling. If the X span is very large, though, data on the graph can be very compressed so that you may be unable to estimate wether it is correctly updating or not (but this is how your actual graph looks like at the end of the process, if I'm not wrong)

2. using a strip chart instead of a graph: this permits to display a subset of data captured (say last xx minutes) with good level of details. Older data are lost on screen (but you are dumping them on disk) but there is no rescaling of the control so there should not be that distortion you are now seeing



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
0 Kudos
Message 9 of 10
(4,947 Views)

Thanks for this. I needed to do the same thing, and immediately went into this forum and did a search and found exactly what I was looking for.

 

The code I am working with makes extensive use of globals, so my next effort will be adding some kind of resource lock around variables accessed inside the timers. The code has lots of globals and lots of timers, so it should be a fun project.

0 Kudos
Message 10 of 10
(2,692 Views)