1

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 Futures 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?

Will
  • 1,835
  • 10
  • 21
  • 2
    I'm not really sure what you're trying to do here. In your example code, the `afterAll` future `x` is cancelled before it ever starts running. If it did start running, then you wouldn't be able to cancel it until it had completed, because cancellation requires the MATLAB thread. So I can't see how this approach is ever going to be particularly useful. – Edric Feb 21 '23 at 13:10
  • You can use the timer object for “asynchronous” execution. Putting that in quotes because the main thread, by definition, does just one thing at the time. – Cris Luengo Feb 21 '23 at 14:39
  • @Edric my example was overly simplistic but I've updated it to demonstrate that functions called by `afterAll` can still be terminated early by cancellation if you get control of the MATLAB main thread while it is in progress, e.g. with UI interaction – Will Feb 21 '23 at 17:31

1 Answers1

1

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).

Edric
  • 23,676
  • 2
  • 38
  • 40
  • That's a much more appealing pattern! It should be noted that this doesn't produce exactly the same behaviour as in my MCVE because the output of `disp` is captured in the `parallel.FevalFuture.Diary` property. Clearly there are differences in the execution environment. But these will often be unimportant differences. – Will Feb 22 '23 at 10:18