Andreas
Andreas

Reputation: 307

How to create a asynchronous waitbar in MATLAB for a parallel external process?

I need to wait for a process to finish with basically unknown duration. I call this process via:

output = system('foo.cmd')

Process foo is blocking any further execution of code. The process takes often about 10 seconds to finish and does not return any status but its output value afterwards. Sometimes it takes a second, sometimes a minute or more. To simulate the progress to the user I would like to implement a waitbar in advance of the excution, somehow like:

function f = my_waitbar()
    f = waitbar(0,'Please wait...');

    for i=1:10
    pause(1)
        waitbar(i/10,f,'Executing foo');
    end
    waitbar(1,f,'Finishing, please wait...'); % should wait now and do nothing
end

Then after foo.cmd finished I would close the bar.

f = my_waitbar() % blocks my code from execution, therefore is pointless
output = system('foo.cmd')
close(f)

But by just calling function f = my_waitbar() in advance, the process would not be triggered parallel. How do I execute my_waitbar() without waiting for it to be finished?

Note that everything after system('foo.cmd') is depending on its execution, therefore further code execution has to wait for it as it does now, so system('foo.cmd &') would not work well for me.

EDIT

Please assume that I have no influence on the content of foo.cmd

Upvotes: 3

Views: 697

Answers (2)

Hoki
Hoki

Reputation: 11792

As Wolfie already mentionned, a standard progressbar is not suited to wait for a process with unknown duration (or unknown iterations). In these cases you better use a spinner (gif file), a circular waitbar (good choice on the File Exchange: cProgress), or an indefinite progressbar. This is what I used for this example:

enter image description here


Now how to make it possible. Since I cannot replicate your exact process foo.cmd, I replaced it by the dos command dir. So for the base line example:

tic
command = 'dir' ;
[~,cmdout] = system(command) ;
toc

>> Elapsed time is 1.547987 seconds.

1.5 second is enough to notice, and cmdout does indeed contain the list of files and directories. So I'll assume this is as close as your case than can be.

To be able to monitor the end of the process, I will package the call to dir (or in your case the call to foo.cmd) into a batch file which will:

  • Check if a file ("MyProcessIsFinished") exist. If yes delete it.
  • Call the process of interest, redirect the output to a file.
  • Create an empty file "MyProcessIsFinished"

This allow MATLAB to call the batch file without waiting for the result (using &). You then make MATLAB wait until it detects the file. When detected, you know the process is finished, you can close your waitbar and continue execution of your code. You can then read the file containing the results you used to get in cmdout.

% initialise flag
processFinished = false ; 

% start the continuous waitbar
hw = mywaitbar(0.5,'Please wait','Waiting for response ...',true);

% call the process in the batch file, without waiting for result
command = 'mycommand.bat &' ;
system(command) ;

% wait for the process to be finished
while ~processFinished
    if exist('MyProcessIsFinished','file')
        processFinished = true ;
    end
end
close(hw) ; % close the wait bar

% now read your results
cmdout = fileread('outputfile.txt') ;

The file mycommand.bat is now:

@echo off
if exist MyProcessIsFinished del MyProcessIsFinished
dir > outputfile.txt
copy nul MyProcessIsFinished > nul
exit

Don't forget to replace the line dir > outputfile.txt by a call to your process and a redirection to a suitable file name. It could look like:

foo.cmd > ReceivedRequest.json

The continuous waitbar: I picked up mywaitbar.m from the file exchange: mywaitbar. The code is nice but I had to change a couple of things to improve the timer management so if you want a working version there will be a couple of changes:

  • Line 109: Add 'CloseRequestFcn',@closeRequestFcn to the properties of the new figure.
  • Line 120: Add 'Name','CircularWaitbarTimer' to the properties of the new timer

Then at the bottom of the file, add the following function:

function closeRequestFcn(hobj,~)
    % Delete the timer
    t = timerfindall('Name','CircularWaitbarTimer') ;
    if strcmpi('on',t.Running)
        stop(t) ;
    end
    delete(t)
    delete(hobj)

That will make a more stable waitbar utility and will get rid of the annoying warning/error messages about timers not managed properly.

Upvotes: 3

Wolfie
Wolfie

Reputation: 30047

It's not a great UX choice to have a percentage-based progress bar which may "complete" long before your process does, or never complete because your process finishes sooner. See this question on UX.stackexchange which discusses the alternatives. In short, a spinner is more conventional.

You can create a custom load spinner fairly easily. This uses an undocumented but simple method to display an animated gif within a figure without having to use code to advance the frames (which would not work for an asynchronous task!).

There are several options for animated gifs already shipped with MATLAB, I've chosen to use this one:

loader

Here is the code and the result (note that in reality the result is animated!)

function hFig = loadspinner()    
    % Main path for installed MATLAB images (and other files)
    fp = fullfile(matlabroot,'toolbox','matlab');
    % Path to the image we want, there are other options in this folder...
    fp = fullfile(fp, 'sourcecontrol\release\images\spinner.gif');

    % Create the figure    
    hFig = figure('Color', 'w', 'NumberTitle', 'Off', ...
                  'Resize', 'Off', 'Menubar', 'None');
    % Get image size to reduce hard-coding in case we change image
    sz = size( imread( fp ) );
    % Insert the animated gif into a HTML pane to enable the animation
    je = javax.swing.JEditorPane('text/html', ['<html><img src="file:/', fp, '"/></html>']);
    [~, hc] =  javacomponent(je,[],hFig);
    % resize figure and image
    hFig.Position(3:4) = [220,sz(2)+35];
    set(hc, 'pos', [(220-sz(1))/2-2,6,sz(1)+4,sz(2)+4])
    % Add text
   annotation( hFig, 'textbox', [0,0.9,1,0], ...
                'String', 'Loading, please wait...', ...  
                'LineStyle','none', ...
                'margin', 0, ...
                'verticalalignment', 'top', ...
                'horizontalalignment', 'center' );
end

result

You could use this the same as you showed for the waitbar

f = loadspinner();
output = system('foo.cmd')
close(f);

Upvotes: 3

Related Questions