Search code examples
matlabasynchronousbackground-processing

Can MATLAB create a Future for executing a function without ever invoking a function on a worker?


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?


Solution

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