JustinBlaber
JustinBlaber

Reputation: 4650

How to prevent a callback from being interrupted by a figure's close function?

Was wondering how the question in the title can be achieved. I have some callbacks that run from button presses. These callbacks, if interrupted by the figure close, will cause errors because the function seems to run, then gets interrupted by the close function which closes the figure, and then the callback seems to resume after the figure is closed.

If I set the button's 'Interruptible' property to 'on', it prevents other callbacks from interrupting it, but does not seem to work for the figure's close function. Another idea I had was to specify the 'closefunction' in the figuring's 'CloseRequestFcn' callback and then call drawnow to flush the eventqueue before deleting the figure but this did not work.

The last resort for me is to simply set the figure's 'CloseRequestFcn' to '' when running callbacks but this seems tedious. Is there a standard solution to accomplish this?

EDIT:

From matlab's documentation:

Note If the interrupting callback is a DeleteFcn or CreateFcn callback or a figure's CloseRequest or ResizeFcn callback, it interrupts an executing callback regardless of the value of that object's Interruptible property. The interrupting callback starts execution at the next drawnow, figure, getframe, pause, or waitfor statement. A figure's WindowButtonDownFcn callback routine, or an object's ButtonDownFcn or Callback routine are processed according to the rules described above.

So it appears to be the case that the interruptible property doesn't effect the close function.

EDIT 2:

Ok, so I think I found a problem. It's really bizarre. I actually discovered from the matlab documentation that callbacks are only interruptible if they have the interruptible property set to on AND :

If there is a drawnow, figure, getframe, waitfor, or pause command in the running callback, then MATLAB executes the interrupting callbacks which are already in the queue and returns to finish execution of the current callback.

I don't use any of these functions explicitly, so it turns out most of my callbacks aren't interruptible by the closereqfcn. BUT, it turns out some are, and the reasons why seem very strange. If have a callback with:

`large computation -> imshow -> imshow

large computation -> set -> set -> set -> set

where the set command is setting the axes visible property to off, then no interruption seems to occur if I exit during the callback

Now, if I have:

large computation -> imshow -> set -> imshow -> set

matlab issues an error if I exit during the callback on the second set command. Also, if I have:

large computation -> imshow -> imshow -> set

matlab issues an error if I exit during the callback on the first set command.

large computation -> imshow -> imshow -> imshow

also issues an error on the third imshow if I cancel during the callback.

For some reason it seems that two successive calls to imshow makes my callback interruptible. Is it possible matlab implicitly calls drawnow or does something weird if you use multiple imshows? BTW, my matlab version is R2009a.

Upvotes: 3

Views: 2850

Answers (2)

p8me
p8me

Reputation: 1860

An alternative to @Rody Oldenhuis's solution is to start a timer inside the CloseRequestFcn to close the figure when no uninterruptible code is in progress (which could be indicated by a flag; Closing_Allowed).

function mainFig_CloseRequestFcn(hObject, eventdata, handles)

    Time = 3; % Wait time before force killing (in sec)
    Kill.tmr = timer('executionMode', 'fixedRate',...
        'Period', 1/10,...
        'TimerFcn', {@KillingTimer_Callback, handles});
    Kill.counts = ceil(Time/Kill.tmr.Period);

    setappdata(handles.mainFig,'Kill',Kill);

    start(Kill.tmr);

function KillingTimer_Callback(hObject, eventdata, handles)

    Kill = getappdata(handles.mainFig,'Kill');
    Kill.counts = Kill.counts - 1; % Count down
    setappdata(handles.mainFig,'Kill',Kill);

    if Kill.counts == 0 || getappdata(handles.mainFig, 'Closing_Allowed')
        stop(Kill.tmr);
        delete(handles.mainFig);
    end

if Kill.counts == 0 means time out, and closes the figure even if an uninterruptible task is in progress, which then would result in the same errors you get sometimes now, but if you know the maximum amount of time you need to finish the uninterruptible jobs, then you can set the Time above properly.

Finally wrap the uninterruptible code by setting the Closing_Allowed flag.

function pushbutton_Callback(hObject, eventdata, handles)

    setappdata(handles.mainFig, 'Closing_Allowed', 0); % Closing is not allowed
    pause(2);
    setappdata(handles.mainFig, 'Closing_Allowed', 1); % Closing is allowed

Upvotes: 0

Rody Oldenhuis
Rody Oldenhuis

Reputation: 38032

I never really trusted that Interruptible flag (or comparable mechanisms)...I immediately admit I have never used it a lot, but that was because when I was experimenting with it for the first time, I noticed that 'Interruptible''off' (and friends) seemed to have more exceptions to the rule than vindications of it -- headache material alert!

So, I got in the habit of tackling this sort of problem simply by using flags, and wrapping all callbacks that must really be uninterruptible in a locking/releasing function.

Something like this:

% Define a button
uicontrol(...
    'style', 'pushbutton',...
    'interruptible', 'off',... % Nice, but doesn't catch DeleteFcn, CreateFcn, ...
                               % CloseRequestFcn or ResizeFcn
    % ...
    % further definition of button 
    % ...

    % Put callback in a wrapper:
    'callback', @(src,evt) uninterruptibleCallback(@buttonCallback, src,evt)...
);

where uninterruptibleCallback() looks something like this:

function varargout = uninterruptibleCallback(callback, varargin)

    % only execute callback when 'idle'
    % (you can omit this if you don't want such strict ordering of callbacks)
    while ~strcmp( get(mainFigure, 'userData'), 'idle' )
        pause(0.01);
        % ...or some other action you desire
    end

    % LOCK
    set(mainFigure, 'userData', 'busy');

    try
        % call the "real" callback
        [varargout{:}] = callback(varargin{:});

        % UNLOCK
        set(mainFigure, 'userData', 'idle');

    catch ME
        % UNLOCK
        set(mainFigure, 'userData', 'idle');

        throw(ME);
    end

end

Which allows you to use this closeReqFcn() for your figure:

function closeReqFcn(~,~)

    % only when the currently running locked callback (if any) has finished
    while ~strcmp( get(mainFigure, 'userData'), 'idle' )
        pause(0.01);
        % ...or some other action you desire
    end

    % ... 
    % further clean-up tasks
    % ... 

    % deletion
    delete(mainFigure);

end

Theoretically, when you put all callbacks in this sort of schema, it is basically equal to managing your own event queue.

This of course has a few advantages, but many, many drawbacks -- you might want to think this through for a bit. This whole mechanism might be unacceptably slow for your use case, or you might need to define a few more locking functions with far more specific behavior.

In any case, I suspect it's a good place to start off from.

Upvotes: 2

Related Questions