- Undocumented Matlab - https://undocumentedmatlab.com -

Controlling callback re-entrancy

Posted By Yair Altman On August 10, 2011 | 17 Comments

I’d like to welcome guest blogger Malcolm Lidierth [1] of King’s College London. Malcolm is well known in the Matlab-Java community for his jcontrol utility [2]. Some months ago, I mentioned his work on another File Exchange contribution, MUtilities [3] when I discussed transparent Matlab figure windows [4]. Today, Malcolm discusses one of his lesser-known but extremely important isMultipleCall utility [5].
Every now and again, a very simple bit of code turns out to be more useful than the author initially imagined. Something I have repeatedly used is the isMultipleCall function which I posted [5] to MATLAB Central’s File Exchange a year or so ago.
The isMultipleCall function uses fully-documented pure-MATLAB to extend the control that can be achieved over callbacks.
Here was the problem: I had a modular system built in MATLAB which allowed third-party developers to add their own plugins. I wanted a mechanism to force the dismissal (“bail-out”) of a callback even when the Interruptible property of the parent object was set to ‘on’. Such callback re-entrancy issues are common for rapidly-firing events, and debugging and fixing them is usually not easy [6].
The callback’s dismissal code would need to be fast because it might be called many dozens of times, e.g. in a WindowButtonMotion callback. An obvious approach was to check the function call stack using MATLAB’s dbstack function. Although, at first, this seemed likely to be too slow, profiling showed it was not – taking < 40µsec per call – and within a WindowButtonMotion callback in a real GUI, I could not perceive any slowing of the code.
Here is the function:

function flag=isMultipleCall()
  flag = false;
  % Get the stack
  s = dbstack();
  if numel(s)< =2
    % Stack too short for a multiple call
    return
  end
  % How many calls to the calling function are in the stack?
  names = {s(:).name};
  TF = strcmp(s(2).name,names);
  count = sum(TF);
  if count>1
    % More than 1
    flag = true;
  end
end

With isMultipleCall invoked from another function (see note below), dbstack will return a structure with a minimum of 2 elements – the first relating to isMultipleCall itself and the second to the calling function. So with numel(s) <= 2, there can be no multiple calls and we can return false immediately thus saving time in doing any further testing. For numel(s) > 2 we simply check to see whether the calling functions referenced in s(2) appears anywhere else on the stack. If it does, then we return true; otherwise false.
Then, in our callback code we simply use:

if isMultipleCall();  return;  end

If this line is placed first in the callback function code, it essentially mimics the behavior that you might expect after setting the Interruptible property of the event firing object to ‘off’. Adding a drawnow() at the end of the callback will ensure that any waiting callbacks in the queue are dismissed:

function MyCallback(hObj, EventData)
  % Quick bail-out if callback code is called before another has ended
  if isMultipleCall();  return;  end
  ...  % do some actual callback work here
  drawnow();
end

There are several ways in which isMultipleCall can extend the standard MALAB functionality. First, by moving isMultipleCall reference from the first line of the callback we can create both an interruptible and an uninteruptible code block, e.g.

function MyCallback(hObj, EventData)
  %Code Block 1
  ...
  if isMultipleCall();  return;  end
  %Code Block 2
  ...
  drawnow();
end

Second, as isMultipleCall controls the callbacks – not the objects that trigger them – we can individually control the callbacks of objects which fire multiple events. That is particularly useful with Java components, which gives a third extension – isMultipleCall can be used in any function: not just the callbacks of standard MATLAB components, but also of Java or COM components.

Finally, as the callback, not the object is being controlled, we can control a callback that may be shared between multiple objects e.g. a menu component and a toolbar button.
Not bad for 13 lines of code.
Note: isMultipleCall must be called from a function, not from a string in the callback property.
Do you have any other favorite mechanism for controlling callback re-entrancy? If so, please post a comment [7].

Categories: Guest bloggers, GUI, Low risk of breaking in future versions


17 Comments (Open | Close)

17 Comments To "Controlling callback re-entrancy"

#1 Comment By Mikhail On August 10, 2011 @ 12:44

My approach is instead of calling

varargout = func( varargin )

inside of callback, use following syntax:

varargout = func_queue( @func, varargin )

where function

func_queue()

is responsible for interrupting if called multiple times and always caching last varargin so that it is not getting lost.

#2 Comment By Arda On August 11, 2011 @ 07:30

persistent returnFlag
if ~isempty(returnFlag)
   return
end
returnFlag=1;
...do something
returnFlag=[];

6 lines 🙂

#3 Comment By Malcolm Lidierth On August 11, 2011 @ 14:58

@Arda – that’s certainly faster by about 10-fold, but would be less easy to maintain e.g. with multiple return points. Perhaps the challenge should be to achieve this with less than one line in the callback!

#4 Comment By Arda On August 12, 2011 @ 11:20

@Malcolm, it was just a joke. Sorry for the misunderstanding. Yet the code i wrote above does not work as i suspected..

#5 Comment By Malcolm Lidierth On August 12, 2011 @ 12:50

@Arda
Your code worked for me in a WindowButtonMotionFcn relacing …doSomething with

 line(rand(1,10),rand(1,10));
drawnow();

That example simulates the effect of setting ‘Interruptible’ to ‘off’ and ‘BusyAction’ to ‘cancel’ – but via the callback rather than the object. If isMultipleCall() took up a large proportion of the execution time, an approach like this would be far better – the processor overhead for your code is tiny. Another tack is to clear the callback property at the start and reset it at the end of the callback routine – but if any error or ^C occurs while its cleared how would you reset it?

#6 Comment By Yair Altman On August 13, 2011 @ 15:15

@Malcolm, you cannot clear the callback property value from within an executing callback, AFAIK. At least, it didn’t work a few releases ago and since it made sense I did not try again lately. The property appears to change, but then reverts to its previous value when the callback ends.

#7 Comment By Malcolm Lidierth On August 14, 2011 @ 07:26

@Yair
That surprises me. You certainly can with a figure WIndowButtonMotion callback The code below runs just once after using set(figH, ‘WindowButtonMotionFcn’, @MyCallback).

 
function MyCallback(hObject, EventData)
set(hObject, 'WindowButtonMotionFcn', []);
disp('In Callback');
return
end

#8 Comment By Christos Oreinos On March 7, 2017 @ 18:32

I also used a solution almost similar to Arda’s (using a persistent variable). After reading this post I opted for the isMultipleCall() because, like Malcolm very very wisely says, I had problems when internal callback errors (which were not fatal in my case) or Ctrl+C stopped the callback that I was controlling for recursive call. In this case the callback could not be run again unless I explicitly cleared all variables.

#9 Comment By Arda On August 20, 2011 @ 12:40

By the way, if the handle has properties ‘BusyAction’ and ‘Interruptable’ (like figure windows, uicontrols, etc.), setting ‘cancel’ and ‘off’ is always a choice. That should do the same…

#10 Comment By Michele On February 2, 2012 @ 01:31

This utility is very interesting, but due to the use of ‘dbstack’ it wouldn’t work in compiled applications.
Do you know of any easy workaround for that?
Thank you very much

#11 Comment By Yair Altman On February 2, 2012 @ 01:42

@Michele – you could store a temporary flag in a persistent variable or the handle’s ApplicationData/UserData, or a variant of these. For example:

% Variant1
function myCallbackFcn1(hObject,eventData,varargin)
   persistent inCallback
   if ~isempty(inCallback),  return;  end
   inCallback = true;
   try
       % do something useful here
   catch
       % error trapping here
   end
   pause(0.001); drawnow;  % give all other events a chance to bail out above
   inCallback = [];
end  % myCallbackFcn1

% Variant2
function myCallbackFcn2(hObject,eventData,varargin)
   inCallback = getappdata(hObject,'inCallback');
   if ~isempty(inCallback),  return;  end
   setappdata(hObject,'inCallback',true);
   try
       % do something useful here
   catch
       % error trapping here
   end
   pause(0.001); drawnow;  % give all other events a chance to bail out above
   setappdata(hObject,'inCallback',[]);
end  % myCallbackFcn2

#12 Comment By Michele On February 2, 2012 @ 01:49

@Yair – Thank you very much for the quick reply, I’ll definitely give it a try.

#13 Comment By Rafael On May 31, 2013 @ 15:58

This is very interesting. But how would you handle the case when you don’t want to bail out of the callback if it’s already executing, but you want to wait instead for the first instance to finish? The obvious solution of putting a polling loop does not work because the loop will keep Matlab from finishing the execution of the first instance. I can’t seem to find a solution to that problem!
Thanks!

#14 Comment By Yair Altman On June 1, 2013 @ 10:41

@Rafael – if the callback’ed object supports the BusyAction and Interruptable properties, then setting these would be the easiest choice. Otherwise, you could preserve the callback parameters in some persistent data struct (e.g., a cell array) and set a timer object to run the callback function programmatically in a second or two.

#15 Comment By Rafael On June 3, 2013 @ 13:56

Thanks a lot for the answer @Yair!
I’m not sure your solution of using a timer to re-execute the function will work for me. I have thought of that solution for a while, but let’s say you have two things that must happen in a particular order, and only the first one is the one that must be re-executed by the timer. How do I hold the execution of the second process until the first one has for sure executed? I get back to the polling loop problem! Would I have to convert all of my processes into timers that are constantly firing to check if it’s OK to execute in the right order? My whole system would be come a nightmare if I have to do that!

#16 Comment By Elliott On February 20, 2014 @ 14:18

This has been a really useful post and has really helped out a Matlab GUI that I have.

I’m confused on one count though, and I’m sure this is just my lack of understanding of how Matlab runs behind the scenes. Why does this not work with a figure’s ResizeFcn callback? I would really like to have a custom resize, but it needs to be safe against re-entrancy. Currently, I’d handle this with a timer, but I’d rather not have to use that.

Is there a way to get this to work with the ResizeFcn?

Thanks.

#17 Comment By Jan On July 29, 2020 @ 20:13

Is there a reason why this function was removed from the Matlab File Exchange?

I prefer a simplified DBSTACK solution:

function flag = isMultipleCall()
   s = dbstack();
   flag = (numel(s) > 2) && (sum(strcmp(s(2).name, {s.name})) > 1);
end

Article printed from Undocumented Matlab: https://undocumentedmatlab.com

URL to article: https://undocumentedmatlab.com/articles/controlling-callback-re-entrancy

URLs in this post:

[1] Malcolm Lidierth: http://www.mathworks.com/matlabcentral/fileexchange/authors/23816

[2] jcontrol utility: http://www.mathworks.com/matlabcentral/fileexchange/15580-using-java-swing-components-in-matlab

[3] MUtilities: http://www.mathworks.com/matlabcentral/fileexchange/28326-mutilities

[4] transparent Matlab figure windows: http://undocumentedmatlab.com/blog/transparent-matlab-figure-window/

[5] isMultipleCall utility: http://www.mathworks.com/matlabcentral/fileexchange/26027-ismultiplecall

[6] not easy: http://www.mathworks.com/matlabcentral/answers/5809-how-to-implement-a-speed-limit-for-gui-button-press

[7] post a comment: http://undocumentedmatlab.com/blog/controlling-callback-re-entrancy/#respond

[8] Callback functions performance : https://undocumentedmatlab.com/articles/callback-functions-performance

[9] Continuous slider callback : https://undocumentedmatlab.com/articles/continuous-slider-callback

[10] Controlling plot data-tips : https://undocumentedmatlab.com/articles/controlling-plot-data-tips

[11] Inactive Control Tooltips & Event Chaining : https://undocumentedmatlab.com/articles/inactive-control-tooltips-event-chaining

[12] Java stack traces in Matlab : https://undocumentedmatlab.com/articles/java-stack-traces-in-matlab

[13] Matlab and the Event Dispatch Thread (EDT) : https://undocumentedmatlab.com/articles/matlab-and-the-event-dispatch-thread-edt

Copyright © Yair Altman - Undocumented Matlab. All rights reserved.