32
$\begingroup$

Currently we have a limited number of ways to perform asynchronous evaluation.

The most common is through Dynamic and Manipulate.

Using Dynamic, we can have part of a cell update independent of whatever we're working on:

Dynamic["x = " <> ToString@x]
For[i = 0, i < 100, i++, Pause[0.05]; x = i^2];

Similarly, we can use Manipulate to continuously evaluate an expression and output the result:

Manipulate[
 {r, v} = {r + dt v, v - dt r};
 Show[Graphics@Point[r], PlotRange -> {{-2, +2}, {-2, +2}}],
 {{dt, 0.01}, 0.01, 1},

 Initialization -> {
   r = {0, 1};
   v = {1, 0};
   }]

We also have a way to submit jobs and have them work independently of each, however it does block any further input:

jobs = Table[
   ParallelSubmit[
    SingularValueList[RandomReal[1, {1000, 1000}]]],
   {10}];

WaitAll[jobs]

The third option gets the closest to being an asynchronous evaluation, however it fails in that it requires any further input to be blocked. What I would love to see is some function, tentatively named AsynchronousEvaluate, which does exactly what it name says:

AsynchronousEvaluate[Pause[10];]
Print["Hello!"]; (* Printed immediately *)

Is there any way we can get close to achieving this?

My dream is to be able to queue up a job for processing on a parallel kernel with certain constraints (Like MemoryConstrained) and be able to just "set and forget." When processing is finished, the result will be returned to me. But in the mean time I can still be productive by doing something else.

Asynchronous evaluation is the only piece that's missing from this, and I'd like to do it if it's possible.

$\endgroup$
7
  • $\begingroup$ What should be done with the result from AsynchronousEvaluate? $\endgroup$ Jan 19, 2012 at 4:17
  • 1
    $\begingroup$ I would think it would be output below the cell where it started evaluating. ParallelSubmit has this behavior when you call WaitNext or WaitAll. $\endgroup$ Jan 19, 2012 at 4:39
  • $\begingroup$ When you asked this on StackOverflow a month ago, I mentioned Parallel`Developer`QueueRun[] in my answer. Can you please explain why that solution doesn't work for your problem? It seems to me it'd do exactly what you're asking for. $\endgroup$
    – Szabolcs
    Jan 19, 2012 at 7:19
  • $\begingroup$ @Szabolcs: I thought it did what I needed, but after I really looked into it I realized it still had the blocking behavior that WaitNext and WaitAll have. $\endgroup$ Jan 19, 2012 at 7:21
  • 3
    $\begingroup$ @Szabolcs: I can't tell whether this is relevant to OP's issue, but it appears that interprocess communication (SetSharedVariable, SetSharedFunction) is handled through QueueRun. "Asynchronous" processes which evaluate shared expressions will hang until QueueRun is next executed. $\endgroup$ Feb 6, 2013 at 3:19

4 Answers 4

16
$\begingroup$

By using the ideas of @Szabolcs, I've managed to write a convenient function which almost does what you want (edit: there is a better version of this function in the third edit, that does exactly what you want):

ClearAll[AsynchronousEvaluate];
SetAttributes[AsynchronousEvaluate, HoldAll];
AsynchronousEvaluate[exp_] := DynamicModule[{eval, display},
  display = EventHandler[
    eval= ParallelSubmit[exp],
    {"MouseClicked" :> (display = WaitAll[eval])}];
  Parallel`Developer`QueueRun[];
  Dynamic[display]]

What this function does is put the expression you want to evaluate in the queue, and makes it run in the background. In the mean while it displays the EvaluationObject that the function ParallelSubmit outputs, and you are free to perform other operations in the same Mathematica notebook.
Once the evaluation of the expression is complete, all you have to do is click the EvaluationObject, and it will be replaced by the result.

Of course it would be better if the EvaluationObject would be automatically replaced by the result when the evaluation finishes, but I don't know how to do that. If anyone thinks of way - feel free to edit my answer and add it (edit: I already found a way, see below).

Edit: By using $Pre = AsynchronousEvaluate; you can get Mathematica to automatically apply the function AsynchronousEvaluate to every expression you evaluate, saving you some time and making the notebook look neater.

Edit #2: Be careful when assigning a function to $Pre though, the only way to change it back is to restart Mathematica, since trying to evaluate $Pre=. will result in sending the command to one of the parallel kernels :)

Edit #3: OK here it is, the same function, only now it automatically replaces the EvaluationObject by the result when the evaluation finishes. In order for it to work you need to have a scheduled task running in the background:

qRunTask = CreateScheduledTask[Parallel`Developer`QueueRun[]];
StartScheduledTask[qRunTask];

Keeping the default interval of one second for the scheduled task seems reasonable. Now the AsynchronousEvaluate function:

ClearAll[AsynchronousEvaluate];
SetAttributes[AsynchronousEvaluate, HoldAll];
AsynchronousEvaluate[exp_] := DynamicModule[{eval,display},
display = eval = ParallelSubmit[exp];
Dynamic[
  If[MatchQ[eval[[4]],
    Parallel`Developer`finished[_]], display = eval[[4]][[1]]]; display]]

That's it!
If you're not using this function for a while, you can stop the scheduled task (though it doesn't cause any slow down in my experience):

StopScheduledTask[qRunTask];

Edit #4: I improved the function to fix a problem where the results did not persist after restarting Mathematica. Now they do.

$\endgroup$
14
$\begingroup$

First regarding Dynamic and related functions:

If I understand right, the Mathematica kernels has two evaluation "channels", one used by main evaluations submitted by SHIFT-ENTER, and one for Dynamic things (called preemptive evaluations). Preemptive evaluations can interrupt main evaluations, but we still have only two "channels", and an already running preemptive evaluation can't be interrupted again. So this is not suitable for asynchronous evaluation.


When you asked a very similar question on StackOverflow, I pointed you to Parallel`Developer`QueueRun[]. You have not explained in this new question whether that works for the problem, but it seems to me that it answers the question "How to run a job in the background without blocking further input?" perfectly.


The original answer:

I have next to zero experience with parallel computation in Mathematica, so this might not be the best way, but this is what I managed to dig up from the docs:

Launch a kernel:

In[1]:= LaunchKernels[1]

Out[1]= KernelObject[1, "local"]

Submit some long to finish job:

In[2]:= job = 
 ParallelSubmit[First@SingularValueList[RandomReal[1, {2000, 2000}]]]

Mathematica graphics

Start job:

In[3]:= Parallel`Developer`QueueRun[]

Out[3]= True

Now the job is running in parallel in the background ...

Mathematica graphics

... and we're free to do whatever we want in the main kernel. If I understand your question, this is what you needed. We can run Parallel`Developer`QueueRun[] again to check which parallel evaluations have finished (the display of the evaluation object will dynamically update).

In[4]:= 1 + 1

Out[4]= 2

Wait until the evaluation finishes (if it hasn't yet) and collect the result:

In[5]:= WaitAll[job]

Out[5]= 1000.23

Mathematica graphics

$\endgroup$
3
  • $\begingroup$ hm, this doesn't seem to work on my machine. let me try to see what is going on $\endgroup$
    – acl
    Jan 19, 2012 at 11:10
  • $\begingroup$ @acl What happens on your machine? It works here in 8.0.4 in a fresh kernel, but once it happened that the EvaluationObjects didn't render right. They did work but they didn't render as shown here. I still don't know why, today they work fine. $\endgroup$
    – Szabolcs
    Jan 20, 2012 at 23:37
  • $\begingroup$ hm, good question. it works fine now. who knows what happened yesterday. very useful, +1 $\endgroup$
    – acl
    Jan 20, 2012 at 23:48
7
$\begingroup$

Using the ideas of Tobias and Joe, I've written a function that allows to evaluate a function on a parallel kernel while keeping the main kernel free for other things, and a callback is called with the result of the computation on the parallel kernel as argument.

This basically is asynchronous programming. I've spent a lot of time debugging this and it was quite hard learning about parallel programming in Mathematica, but it works now !

The key is to have two scheduled tasks running while a parallel computation is running on a parallel kernel. One that does Parallel`Developer`QueueRun[] for handling the communication between a parallel kernel and the main kernel for shared functions, and one that checks the completion of the computation and calls a callback function once the computation is done.

The functions ExecuteWhen and AsynchronousEvaluate below are very useful in the context of asynchronous programming.

Example

LaunchKernels[1]  

SetSharedVariable[ff];
ff=2;

SetSharedFunction[f];
f[x_]:=ff+=x;

g[x_]:=x+3;
DistributeDefinitions[g];

AsynchronousEvaluate[f[1]+g[2],"Callback"->Print]

Code

SetAttributes[ExecuteWhen,HoldAll];
Options[ExecuteWhen] = {"WaitInterval"->1,"WaitLimit"->300};
ExecuteWhen[condition_,action_,opts:OptionsPattern[]]:= 
    executeWhen[{condition,action},opts];

(*Aux functions so that options are not held (else the function cannot execute when it should)*)
SetAttributes[executeWhen,HoldFirst];
executeWhen[{condition_,action_},opts:OptionsPattern[ExecuteWhen]]:=
    If[condition,
        action
        ,   
        With[{startTime=AbsoluteTime[],waitLimit=OptionValue@"WaitLimit"},      
            RunScheduledTask[
                Which[
                    condition,
                        RemoveScheduledTask[$ScheduledTask];
			action
			,
			AbsoluteTime[]-startTime>waitLimit,
			RemoveScheduledTask[$ScheduledTask];
                ]
                ,
                OptionValue@"WaitInterval"
            ];
        ];
    ];

(*http://mathematica.stackexchange.com/a/5274/66
http://mathematica.stackexchange.com/a/13528/66
http://reference.wolfram.com/legacy/applications/parallel/Tutorial/Concurrency.html
http://blog.wolfram.com/2009/03/18/the-evolution-of-parallel-computing-with-mathematica/
*)
$NumberOfMonitorQueueCalls = 0;
	$QueueMonitorInterval = -1;
MonitorQueue[checkInterval_:1]:=
    (
        Needs["Parallel`Developer`"];

        If[$QueueMonitorInterval==-1 || checkInterval<$QueueMonitorInterval,
            If[ValueQ[$qRunTask],
			StopScheduledTask[$qRunTask];
                ResetScheduledTask[$qRunTask,checkInterval];
					,
					$qRunTask = 
                    CreateScheduledTask[
                        Parallel`Developer`QueueRun[]
                        ,
                        checkInterval
                    ];
            ];
        ];

        Parallel`Developer`QueueRun[];
        StartScheduledTask[$qRunTask];      

        $NumberOfMonitorQueueCalls++
    );

StopMonitorQueue[]:=
    (
        If[$NumberOfMonitorQueueCalls>0,
				$NumberOfMonitorQueueCalls--;

            If[$NumberOfMonitorQueueCalls == 0,
					StopScheduledTask[$qRunTask];
            ];
        ];

        $NumberOfMonitorQueueCalls
    );

SetAttributes[AsynchronousEvaluate,HoldFirst];
Options[AsynchronousEvaluate] = 
    Join[
        {
            "AsynchronousCall"->True,
            "Callback"->Identity,
            "MonitorQueueInterval"->1
        }
        ,
        Options[ExecuteWhen]
    ];
AsynchronousEvaluate[parallelAction_,opts:OptionsPattern[]]:=
    Module[{pid, resultCallback,executeWhenOptions}, 

        Needs["Parallel`Developer`"];

        resultCallback = OptionValue@"Callback";

        If[OptionValue@"AsynchronousCall" && Kernels[] =!= {},

            pid = ParallelSubmit[parallelAction];

            (*MonitorQueue is useful for shared functions and variables called from a parallel kernel*)
            MonitorQueue[OptionValue@"MonitorQueueInterval"];

            executeWhenOptions = FilterRules[{opts},Options[ExecuteWhen]]; 

            With[{pid=pid,resultCallback=resultCallback,executeWhenOptions=executeWhenOptions},
                ExecuteWhen[
                    MatchQ[Parallel`Developer`ProcessState[pid],Parallel`Developer`finished[_]]
                    ,
                    StopMonitorQueue[];
                    resultCallback[First@Parallel`Developer`ProcessResult[pid]]
                    ,
                    executeWhenOptions
                ];
            ];

            (*we return the pid so that other functions can also monitor this evaluation*)
            pid
            ,
            resultCallback[parallelAction]
        ]
    ];
$\endgroup$
4
$\begingroup$

If it is not important that you share variables during the calculation, maybe one solution would be to start a separate kernel to calculate (using LinkLaunch) and have the asynchronous calculation run there.

One disadvantage of this method is of course that it uses up another kernel license.

$\endgroup$
2
  • $\begingroup$ Interesting. I'll have to look into that. I don't mind using up a second kernel license. $\endgroup$ Jan 19, 2012 at 6:59
  • $\begingroup$ @Mike If you want to reimplement this yourself, independent of the existing Parallel* functionality, a good starting point is this doc page. It shows you how to launch another kernel, send an expression to it to evaluate and retrieve the result. To make this practically useful (for general use), you'd need to implement something like DistributeDefinitions as well. When using links, take care to check them with LinkReadyQ before reading from them to avoid freezing Mathematica up. $\endgroup$
    – Szabolcs
    Jan 19, 2012 at 14:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.