Reputation: 307
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
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:
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:
"MyProcessIsFinished"
) exist. If yes delete it."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:
'CloseRequestFcn',@closeRequestFcn
to the properties of the new figure.'Name','CircularWaitbarTimer'
to the properties of the new timerThen 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
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:
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
You could use this the same as you showed for the waitbar
f = loadspinner();
output = system('foo.cmd')
close(f);
Upvotes: 3