In App Designed app: When does a function called within another function spawn a new thread?

I'm having SO much trouble! I think maybe when I call a function from within another function or callback it's going into a new thread.
I want it to finish the function before moving on. Is the best way to do that to make the function return a (dummy) value?
Since there is nothing to return but completion do I need to declare
function finish = MyFunction(app);
does finish need a value assigned at the end?
or just short cut and call
fin = MyFunction %#ok suppress mLint about unused return value?

12 Comments

Hi @Gavin,

You mentioned, “I'm having SO much trouble! I think maybe when I call a function from within another function or callback it's going into a new thread. I want it to finish the function before moving on. Is the best way to do that to make the function return a (dummy) value?Since there is nothing to return but completion do I need to declare”

Please see my response to your comments below.

In MATLAB, when you call a function from within another function or a callback, it does not automatically create a new thread; however, the execution order can sometimes be confusing, especially with asynchronous callbacks. To ensure that the calling function waits for the called function to complete, you do not need to return a dummy value. You can simply define your function as follows:

function MyFunction(app)
  % Your code here
end

If you do not need to return any value, you can call it without assigning it to a variable:

MyFunction(app);

Now, if you want to suppress warnings about unused return values, you can use the %#ok comment as you suggested:

fin = MyFunction(app); %#ok

However, it is not necessary to declare a return variable if you do not intend to use it. Just make sure that your function completes its execution before moving on to the next line of code in the calling function.

Hope this helps.

"I'm having SO much trouble! I think maybe when I call a function from within another function or callback it's going into a new thread. I want it to finish the function before moving on. Is the best way to do that to make the function return a (dummy) value?"
Your question hints that making the transition from linear code evaluation (e.g. calling functions from the command line) to asynchronous code evaluation (e.g. all GUIs) is a bit confusing. That is understandable, they are evaluated in completely different ways. It also means that how to manage them also works in completely different ways.
Returning an output is unlikely to make much difference, it is unrelated to asynchronous code control.
My guess is that you are looking for this:
Thanks for the help. You are right that assigning a value to the function doesn't help.
Not even this helped:
function wt = RunTrial(app,trlNum)
% . . . do the trial
wt = true; % complete!
end
In the rerun routine I put
for TrlNum = 1:sz
if strcmp(app.RandTrialsTable.Data.Var4(TrlNum),"-1")
fin = false; %#ok
fin = RunTrial(app,TrlNum);
while ~fin % Stop multiple trials running at once
end % Continue processing
end
end
Yet it is still spawning multiple trial before the last one finished, bouncing the trial number around (RunTrial displays the trial#)
I'm studying the link about callback execution, I think that might help. Order is important but I have to think about which functions can't be intterupted.
Being able to set BusyAction or Interruptable would be great but here is the problem with that:
PicoInput() the critical function that reads from the USB port really should not be interrupted but since it isn't a callback from a figure or button I can't make it a component that I can see in the browser. It's just a function I declared.
function PicoInput(app,src,~)
while (app.PicoCom.NumBytesAvailable>0)
raw = readline(src);
% Do lots of stuff including calling
ManageResults(app,app.TrialNumberField.Value, ...
app.PicoType,app.PicoData);
end
end
Set up like this
configureCallback(app.PicoCom,"terminator",@app.PicoInput);
Where would I put 'Interruptible','off'
or in newer syntax presumably ...,Interruptible='off'
So I tried some
waitfor(RunTrial(app,TrlNum);
But
Error using MouseOdor12/RunTrial
Too many output arguments.
Error in MouseOdor12/ManageResults (line 922)
waitfor(RunTrial(app, trlNum)); % Wait for it to finish
What? I have NO output arguments is this the wrong way to use waitfor()? The example is not helpful, I'n not creating figures and anyway my program is in App Designer that has it's own rules and messes with your code.
Another attempt
waitfor(app.PicoCom,NumBytesAvailable,0); % wait until it's REALLY done
Hung the program (I think there) no way to tell for sure, so I clicked another unrelated button and got this
906 DoEndOfTrial(app,trlNum);
Error while evaluating TimerFcn for timer 'timer-433'
Not enough input arguments.
Error while evaluating TimerFcn for timer 'timer-434'
Not enough input arguments.
But there is no timer function anywhere near line 906, there is a timer function for the button I pushed but that code hasn't changed!
K>> dbstack
> In MouseOdor12/ManageResults (line 906)
In MouseOdor12/PicoInput (line 391)
No help there
The actual timer call (which used to work just fine is
% Schedule a single shot to turn off selection
tRw = timer;
tRw.StartDelay = 0.1;
tRw.TimerFcn = @(~,~)app.RwOff(FL);
start(tRw);
Just turns off the button color later so the user can see it flash.
The timer works just fine unless I'm at a breakpoint (or in the first case hanging without getting to the breakpoint)
Any suggestions? Could this be a bug in MatLab App designed apps? Is there a better place to ask than here?

Hi @Gavin,

To address your concerns about managing function execution order in MATLAB App Designer, it is essential to understand the nature of asynchronous programming and how MATLAB handles callbacks. You probably aware of when a callback (like PicoInput%) is triggered, it executes asynchronously. This means that if another event occurs while the first callback is still running, the second callback may begin execution, leading to overlapping processes. So, to prevent this overlap, it is crucial to implement a mechanism that make sure one callback completes before another starts.

Now to address your query regarding, “Using waitfor function which is typically used to halt execution until a specified condition is met. However, it seems that you encountered issues using waitfor with RunTrial, which does not return an output value. A better approach would be to use a flag or a state variable to indicate whether the function is currently executing. For instance:

     properties
         IsRunning = false;  % State variable
     end
     function RunTrial(app, trlNum)
         if app.IsRunning
             return;  % Exit if already running
         end
         app.IsRunning = true;  % Set running state
         % ... execute trial logic ...
         app.IsRunning = false;  % Reset state when done
     end

Also, setting the Interruptible property of UI components can help manage how callbacks interact. If you want to make sure that certain critical functions (like reading from USB) are not interrupted, make sure they have Interruptible set to off. However, be aware that this will mean other user interactions won't interrupt these critical processes. For non-critical callbacks that might be interrupted, consider using the BusyAction property set to queue, this will make sure that if you trigger another callback while one is executing, the new callback will wait until the current one finishes rather than canceling it.

I saw that you are using dbstack, that cal really help you trace where errors occur in your code. If you encounter an error like "Not enough input arguments", checking the call stack at that point can provide insights into what might be going wrong.

It is frustrating that Asynchronous programming can often lead to complex scenarios in GUI applications where user interactions are frequent. That is why implementing state management techniques (like flags) greatly simplify control flow. Also, I would recommend testing various configurations of the Interruptible and BusyAction properties through trial and error which may help you yield insights into their effects on application responsiveness.

Hope this helps.

@Gavin I think the most useful thing for us it would be to describe what you want to achieve, because I can't follow what each function contains, in which function do you read data, in which function do you process the data.
Try to explain what are you trying to achieve, in a clear way.
You are reading data from the USB device, for which you use serial and readline function.
Documentation on readline says
If the function is unable to return any data within the period specified by the Timeout property of device, it returns data as a 0-by-0 double []. The function suspends MATLAB® execution until the terminator is reached or a timeout occurs.
I don't think readline is an asynchronous operation, therefore nothing is running in parallel.
This comment was flagged by Umar
  • Flagged by Umar on 17 Sep 2024.

    Could you please provide feedback to my recent comments provided below

@Umar are you using AI to write your comments?
I believe that input for serial devices is internally buffered asynchronously, with callbacks queued if the terminator condition is encountered. The callbacks are executed at the beginning of the next line of MATLAB code that is encountered (or immediately if pause() is in effect.)

Hi @Gavin,

In addition to comments provided by @Walter Robertson and @Mario Malic, asynchronous programming allows multiple processes to run concurrently. In MATLAB, when a callback (like reading from a USB device) is triggered, it does not block the execution of subsequent callbacks. This can lead to complications if multiple events occur before the first one has completed. Implementing state management through flags or state variables is crucial. This allows you to prevent concurrent executions of critical functions. The provided code snippet below demonstrates this well:

classdef MyApp
  properties
      IsRunning = false;  % State variable to track if the trial 
      is running
  end
    methods
        function RunTrial(app, trlNum)
            % Check if the trial is already running
            if app.IsRunning
                return;  % Exit if already running
            end
            app.IsRunning = true;  % Set running state
            % Execute trial logic here
            fprintf('Running trial number: %d\n', trlNum);
            pause(2);  % Simulate trial execution time
            app.IsRunning = false;  % Reset state when done
        end
    end
  end

This code checks whether a trial is already running before proceeding, thus preventing overlap.

Please see attached.

I would like @Mario Malic to provide his feedback based on the code snippet provided.

Let's wait for explanation from @Gavin. Once that is clear, we can discuss how to solve the issue.
Alright, then I misinterpreted the documentation about readline.
Thanks for your help guys. Yes I suppose I need more (mutex?) flags to be sure the next trial doesn't start.
I've actually tried similar things (as well as inserting strategic coder.inline('never'); but I'm still having trouble. Could be I put the IsRunning flag in the wrong place so I'll try it again as @Mario Malic outlined above
I called the help desk and outlined my various issues and they (Jack Meehan is lead) are working on it.
I'll let you know when it's resolved but if the details are too much I'll probably just give you the case number (if that's something you can access) if you are interested.
Thanks again.
I just heard from Jack with some suggestions to try. The trial running flag thing didn't work on the first attempt. I need to figure out more places to check it and not start new trials even if they will return immediately. Doing it mindlessly, turning off app.TrialRunning in the EndOfTrial wrapup routine cause a complete hang of App Designer and the program requiring CTRL-ALT-DEL
I've got a few more things to try.

Sign in to comment.

 Accepted Answer

See where @Umar laid it out very well my related issue and provided several solutions to that part of the problem
At issue here was that I was calling the "main line" function ManageResults() from the end of the callback to handle the USB input.
Insightfully, @Umar said above
However, if you re-enter the function that contains a breakpoint while it’s paused (for instance, if the function is called again due to an event), it will hit the same breakpoint again each time it reaches that point in code execution. This could indeed lead to what seems like "piling up" of breakpoints if you continuously trigger the function before resolving the initial breakpoint.
and that is indeed what was happening. It looked like results weren't being processed with a break because of the recursion.
The solution I finally figured out, recalling what I would do in C, and any real time program with interrupts, is that one has to separate the callback function from the completion routine. So the callback just puts the Pico results into a simple circular buffer and then ManageResults() is in it's own loop taking care of whatever is in the buffer and waiting (patiently) to process as needed until the End of trial signal arrives.
As my mother used to say "onward and upward"

1 Comment

Hi @Gavin,
Thank you for sharing your insights regarding the callback function management and the related issues you encountered. I appreciate your detailed explanation of how my feedback helped clarify the situation.
It is indeed crucial to understand the implications of re-entering a function with active breakpoints, as this can lead to unintended complications in code execution. Your approach to separate the callback function from the completion routine is commendable. Utilizing a circular buffer for managing results effectively allows for smoother processing without getting caught in recursion, thereby enhancing system stability. I am glad to hear that you found a solution that resonates with your experience in C programming and real-time systems.
It reflects a thoughtful application of concepts across different programming paradigms.
If there are any further developments or additional assistance you require, please do not hesitate to reach out.

Sign in to comment.

More Answers (1)

I had several confounding issues for example that RunTrial() just starts a trial then returns; so waiting until its really finished in the right spot was important.
@Umar was very helpful in understanding more about callbacks. It is unfortunate that the debugger with dbstack command doesn't help as it can only show the one thread that the breakpoint is in. Is there a way to get info about all the running threads in a running app from the command window?
One remaining question is about pause(1); if I start like this
function RunTrial(app,trlNum) % Don't run until Trial is complete
while app.TrialRunning
pause(1);
AddDebugText(app,"Trial still running");
end
% Continue with setting up the next trial
will the events (USB) keep being processed so the trial can actually finish? (Seems so)
What about if I am running the debbugger in AD? It seems that too much gets halted when I hit a breakpoint or should it be just that thread.
What if I re-enter the function that is paused? Will it hit the same breakpoint again (if the path through the routine with the break is hit) and pile up breaks? I think that is what was confusing me. I'd hit stop and it would just seem to sit at the same point until I stopped again.
I'd need to set a breakpoint that is "out of the way" so the later passes can keep running if needed.
Phew! What a tangle I created!
Thanks for all your help.

1 Comment

Hi @Gavin,

First, let me Thankyou for your compliment, @Umar was very helpful in understanding more about callbacks. After reviewing your recent posted comments, please see my response below.

Let me address your first query, “Is there a way to get info about all the running threads in a running app from the command window?”

Please refer to documentation provided below.

https://www.mathworks.com/matlabcentral/answers/2130561-display-active-threads-that-the-app-uses

Now addressing your query regarding, “One remaining question is about pause(1); if I start like this

function RunTrial(app,trlNum)  % Don't run until Trial is     complete
while app.TrialRunning
  pause(1);
  AddDebugText(app,"Trial still running");  
end
% Continue with setting up the next trial

will the events (USB) keep being processed so the trial can actually finish? (Seems so)What about if I am running the debbugger in AD? It seems that too much gets halted when I hit a breakpoint or should it be just that thread. What if I re-enter the function that is paused? Will it hit the same breakpoint again (if the path through the routine with the break is hit) and pile up breaks? I think that is what was confusing me. I'd hit stop and it would just seem to sit at the same point until I stopped again.I'd need to set a breakpoint that is "out of the way" so the later passes can keep running if needed.”

Please see my detailed response to your second query.

Behavior of pause(1) in Your Code

After reviewing the pause function documentation provided at the link below

https://www.mathworks.com/help/matlab/ref/pause.html

So, analyzing your provided code snippets, when you will use pause(1); inside your RunTrial function, MATLAB will indeed halt execution of that particular thread for 1 second. However, it's important to note that while this thread is paused, other threads (including event handling threads) can continue to process events. This means that USB events or other asynchronous tasks should still be handled, allowing your trial to complete even if the main thread is temporarily inactive. For instance, if you have an event listener set up for USB input, it should still receive and process data during the pause period.

Debugging with Breakpoints

When debugging with breakpoints in MATLAB, only the thread where the breakpoint is hit will be halted. This means that if your application is multi-threaded, other threads can continue executing unless they are also stopped by hitting a breakpoint. However, if you re-enter the function that contains a breakpoint while it’s paused (for instance, if the function is called again due to an event), it will hit the same breakpoint again each time it reaches that point in code execution. This could indeed lead to what seems like "piling up" of breakpoints if you continuously trigger the function before resolving the initial breakpoint.

Setting Breakpoints Strategically

To avoid confusion with multiple breakpoints being hit, you may consider setting conditional breakpoints or placing breakpoints at points in your code where they won't be hit frequently during normal operation. For example, you could place breakpoints at the beginning of a new trial setup rather than within the loop that processes trials. Additionally, using dbstop if error can help catch errors without halting execution unnecessarily on every iteration.

I would advise when you debug complex applications, wrap code sections in try-catch blocks because it can help manage unexpected errors without completely halting execution flow.

Please let me know if you have any further questions for us.

Sign in to comment.

Categories

Find more on Scripts in Help Center and File Exchange

Products

Release

R2023b

Asked:

on 15 Sep 2024

Commented:

on 24 Sep 2024

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!