2

I am running a long data preload within a timer callback, and I would like to be able to stop the callback halfway through with an outside input (for instance, the click of a GUI button). The stop() function will stop the timer calls, but not the callback function itself.

Here is a simple example:

timerh = timer('TimerFcn' , @TimerCallback,'StartDelay' , 1, 'ExecutionMode' , 'singleShot');
NeedStopping = false;

start(timerh)

disp('Running')
pause(1)
disp('Trying to stop')
NeedStopping = true;


function TimerCallback(obj, event)
% Background data loading in here code in here; in this example, 
% the callback simply displays numbers from 1 to 100

    for k = 1 : 100
        drawnow();  % Should allow Matlab to do other stuff
        NeedStopping = evalin('base' , 'NeedStopping');
        if NeedStopping
            disp('Should stop now')
            return
        end
        disp(k)
        pause(0.1)
    end
end

I expect this script to display numbers between 1 and (approximately) ten, but the timer callback does not stop until 100. Strangely, the code reaches the line just before before the pause(1) and correctly prints 'Running', but then it stops there and waits for the timer to finish. Even more perplexing, if I change the 1 second pause to 0.9 seconds, the timer stops immediately with this output:

Running

Trying to stop

Should stop now

I am aware that Matlab is mostly single-threaded, but I thought the drawnow() function should allow it to process other stuff.


Edit: Specific use behind my question: I have a GUI with a "next" button that loads several images and shows them all side by side. The images are large so loading takes time; therefore, while the user looks at the pictures, I want to preload the next set. This can be done in the background with a timer, and it works. However, if the user clicks "next" before the preloading has finished, I need to stop it, show the current images, and launch the preloading for the next step. Hence, the timer needs to stop during callback execution.

5
  • It is quite unclear what you are trying to achieve with this code, and it only losely ressemble the question in the title. In your code you are trying to stop the timer from outside its callback, you are trying to stop it by setting a variable in the base workspace (if that is what you want you could simply do stop(timerh) instead of using a cousin function of eval). We have your code attempt and a description of what it does, now please describe more accurately the behaviour you initialy try to achieve. Commented Apr 17, 2019 at 9:33
  • Thanks for the feedback: I tried to clarify my intent. timer() is not good because it will stop further timer calls, but not the currently running callback. Commented Apr 17, 2019 at 9:40
  • You are right, stopping the timer itself will not stop execution of the callback if it has started already. On the other hand, you are using the singleshot mode of the timer, to start the execution of one single function (the callback), which you would like to be interruptible. You do not need to use a timer for that. The function could be the callback of any button or element of your gui. On the bad news, neither the timer callback or a standard button callback will run in a different process and allow you to do stuff while it's running. Commented Apr 17, 2019 at 9:57
  • Not really; the timer runs in the background in my GUI, and I am able to do other stuff in the meanwhile. But I need to stop it if I want to change dataset before it has finished loading the data for the current one. Commented Apr 17, 2019 at 10:05
  • 1
    yes the timer counts time in the background and let you do other stuff during that time, but when the timer callback actually starts to execute, it takes over the main process and nothing else is going to be excuted in the background. Commented Apr 17, 2019 at 10:25

1 Answer 1

2

This is a demo of how to set up an interruptible callback. The way your example is set up I didn't see the need for an actual timer so I made it as a standard button callback.

note: If you are dead set on using it for a timer you can use exactly the same solution, just assign the startProcess callback to the timer instead of the gui button.

function h = interuptible_callback_demo

% generate basic gui with 2 buttons
h = create_gui ;
guidata( h.fig , h )

% create application data which will be used to interrupt the process
setappdata( h.fig , 'keepRunning' , true )

end


function startProcess(hobj,~)
    h = guidata( hobj ) ;
    % set the 'keepRunning' flag
    setappdata( h.fig , 'keepRunning' , true )
    % toggle the button states
    h.btnStart.Enable = 'off' ;
    h.btnStop.Enable  = 'on' ;

    nGrainOfSand = 1e6 ;
    for k=1:nGrainOfSand
        % first check if we have to keep running
        keepRunning = getappdata( h.fig , 'keepRunning' ) ;

        if keepRunning
            % This is where you do your lenghty stuff
            % we'll count grains of sand for this demo ...
            h.lbl.String = sprintf('Counting grains of sands: %d/%d',k,nGrainOfSand) ;
            pause(0.1) ;
        else
            % tidy up then bail out (=stop the callback)
            h.lbl.String = sprintf('Counting interrupted at: %d/%d',k,nGrainOfSand) ;
            % toggle the button states
            h.btnStart.Enable = 'on' ;
            h.btnStop.Enable = 'off' ;
            return
        end
    end
end

function stopProcess(hobj,~)
    h = guidata( hobj ) ;
    % modify the 'keepRunning' flag
    setappdata( h.fig , 'keepRunning' , false )
end

function h = create_gui
    h.fig = figure('units','pixel','Position',[200 200 350 200]) ;
    h.btnStart = uicontrol('style','pushbutton','string','Start process',...
                            'units','pixel','Position',[50 100 100 50],...
                            'Callback',@startProcess) ;
    h.btnStop  = uicontrol('style','pushbutton','string','Stop process',...
                            'units','pixel','Position',[200 100 100 50],...
                            'Callback',@stopProcess,'enable','off') ;
    h.lbl  = uicontrol('style','text','string','','units','pixel','Position',[50 20 200 50]) ;
end

To see it in action:

enter image description here

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks Hoki, this works and the timer version you suggest is exactly what I am doing in my own GUI. So why doesn'it it work in my minimal script example? BTW, I edited the original post to explain why I resorted to a timer.
not sure exactly but in your example your timer have a StartDelay of 1 second, and then you have a pause(1) instruction just after you start the timer. So the timer and the main process will start to count 1 second in parrallel. If the main process reaches it first (it definitely does when you set the pause to 0.9 second), then it will change the stop flag before the timer actually started, so when the timer finally starts executing callback, it reads the flag to stop and never really do anything.
on the other case, if the timer counts first to 1s, when it's callback starts executing the main process is paused, the drawnow() instruction just flush the graphic event queue, but does not give execution time to the main process ... so the timer keeps counting to 100, then give back control to the main thread which will finally set up your flag stop (but your timer doesn't need it it's already finished).
"drawnow() instruction just flush the graphic event queue, but does not give execution time to the main process". That answers it for me!
In my solution it works because the stop flag is set by another callback, not by the main process. callback functions have a mechanism to interrupt each other, if the right Interruptible property is set. The main process cannot interrupt a callback process.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.