MATLAB's parallel.Future
class provides useful functionality for making responsive code that avoids blocking user input and is able to cancel
previously requested tasks when new input means they are no longer required.
However the only top-level function I know of which returns a Future
object is parfeval
which either runs functions on a parallel pool or on a background worker. This is not suitable for all functions, for example plotting, which require interaction with the main MATLAB process and yields an error if used in the context of a parfeval
call.
However it isn't outright impossible to invoke such functionality asynchronously, because the afterAll
and afterEach
methods that Future
s have execute functions on the main MATLAB process, and are themselves represented by Future
objects.
Thus if we call a completely trivial function on the background pool we can invoke some plotting function immediately afterwards and gain access to control over its execution. For example, the following:
function doTask(ind)
disp("Starting task #" + ind)
for ii = 1:100
plot(rand(5,1))
drawnow
end
disp("Completed task #" + ind)
end
function responsiveCallback(~,evt)
cbo = evt.Source;
n = numel(cbo.UserData);
if n > 0
disp("Cancelling " + n + " Future(s)")
cbo.UserData.cancel;
end
cbo.UserData = [cbo.UserData;
parfeval(backgroundPool,@eye,0) ...
.afterAll(@() doTask(n + 1),0)];
end
uibutton(ButtonPushedFcn = @responsiveCallback);
sets up a button. Clicked just once, it produces the output:
Starting task #1
Completed task #1
Clicked twice in short succession, the output is:
Starting task #1
Cancelling 1 Future(s)
Starting task #2
Completed task #2
Execution of the function that output Starting task #1
is aborted before it has completed, even though it was executing on the main MATLAB thread.
Being forced to do something like parfeval(backgroundPool,@eye,0)
just to call afterAll
is an ugly formulation though; it makes code harder to follow, feels like a hack, and the traffic to/from the background pool might even impact performance in some contexts. Is there a way to produce the same behaviour directly without the parfeval
stage?
In R2021b and later, you can pass an empty parallel.Pool
instance as the first argument to parfeval
to explicitly request execution in the calling process. I.e. use
fut = parfeval(parallel.Pool.empty, @fcn, nOut, args)
This might work for you, but you still have the awkwardness of the co-operative scheduling to deal with (there's only 1 main MATLAB thread, and you need to use drawnow
or similar to yield).