ScheduledTask with scoped variables. Working with timed evaluation
Updated
This happens because your DynamicModule
returns a dynamic object of which x
is passed on to the front-end before the scheduled task starts, so the front-end-x
cannot be modified anymore by any process (more details at the end).
The problem can be further simplified. This works:
RemoveScheduledTask@ScheduledTasks[];
DynamicModule[{x = 0}, RunScheduledTask[x++, {1, 5}]; Print@Dynamic@x;];
but this does not (at least not how one would expect):
RemoveScheduledTask@ScheduledTasks[];
DynamicModule[{x = 0}, RunScheduledTask[x++, {1, 5}]; Dynamic@x]
Examining the second one:
RemoveScheduledTask[ScheduledTasks[]];
d = DynamicModule[{x = 0}, Print[HoldForm@x -> x];
RunScheduledTask[x++, {1, 5}]; Dynamic[HoldForm@x -> x]]
x$1725 -> 0 (* the real name of x inside DynamicModule & the sched.task *) FE`x$$18 -> 0 (* the displayed x as returned from DynamicModule *)
Clearly, the returned x
has nothing to do with the x
used in the scheduled task.
As OP realized, Module
works where DynamicModule
does not. The same analysis as above:
RemoveScheduledTask[ScheduledTasks[]];
d = Module[{x = 0}, Print[HoldForm@x -> x];
RunScheduledTask[x++, {1, 5}]; Dynamic[HoldForm@x -> x]]
x$1740 -> 0 (* the real name of x inside DynamicModule & the sched.task *) x$1740 -> 0 (* the displayed dynamic value of x as returned from DynamicModule *)
The two variables are not decoupled, so the scheduled task will modify the same variable that is displayed. The problem here is that the variable that is actually displayed and modified dynamically (x$1740
) are not owned by the front-end, though it should be according to John Fultz's explanation. See below for a better solution.
Better understanding evaluation sequence
As Leonid has pointed it out, the returned dynamic value of a DynamicModule
is owned by the frontend:
DynamicModule[{x}, Print@Dynamic@x; Dynamic@x]
x$4513 (* printed *) FE`x$$88 (* returned *)
One can change the first one from the outside as x$4513 = 99
, but not the second one, so scheduled tasks failed.
Digging deeper, I managed to get to the depths of the problem. The main reason why the returned x
does not update is because when the return value of the DynamicModule
is returned and displayed on screen, it is passed on to the front end without evaluation, and gets renamed from x$4513
to x$$88
. Only after it is displayed will the front end look after how to update x
, however, the internal scheduled task has already been called before returning Dynamic[x]
from the DynamicModule
, and it still refers to x$4513
, so the two references to x
gets decoupled and will never be linked anymore.
According to this hypothesis, delaying the scheduled task AFTER the returned value is displayed should work. The correct method for this is to use Initialization
, that is evaluated right after the returned value of a DynamicModule
is displayed:
RemoveScheduledTask[ScheduledTasks[]];
d = DynamicModule[{x = 0},
Print["In DM: ", HoldForm@x -> x];
Dynamic[x, TrackedSymbols :> {x}],
Initialization :> (Print["initialized"];
RunScheduledTask[x++; Print["In ST: ", HoldForm@x -> x], {.5, 3}];)
]
When evaluated, one can see that the two Print
statements refer to different variables:
In ST: FE`x$$11->1 In DM: x$1729->0
meaning that the scheduled task (ST
printing) now correctly uses the front-end variable instead of the kernel variable (DM
printing)! For the whole thing to work, one needs an extra TrackedSymbols :> {x}
in Dynamic
, or something similar, to tell the front-end what variable/trigger should be checked. Any of the followings should work:
Dynamic[x, TrackedSymbols :> {x}]
Dynamic@{x}
Dynamic[x, UpdateInterval -> .1]
but not this:
Dynamic@x
The following is a slightly simplified version of your DynamicModule:
DynamicModule[{x},
Column[{Button["start", x = 0; RunScheduledTask[x++;, {1, 5}]],
Row[{Dynamic[HoldForm[x]], " = ", Dynamic[x]}]}]
]
When you evaluate this for the first time, underneath the button you see the output
(* FE`x$$82 = FE`x$$82 *)
(very likely with a different DynamicModule number.) Here FE``x\$\$82 is a kernel variable that is created by the frontend to be able to call the kernel with respect to the frontend variable \$CellContext`x$$, that we see when we inspect the cell expression of the displayed DynamicModule.
When we press the button, the output changes to
(* FE`x$$882 = 0 *)
and nothing indicates that the scheduled tasks were executed. But they are executed! We can ask for the value of the kernel variable:
FE`x$$82
(* 5 *)
Hence we only have to force the updating. That can be done by including TrackedSymbols:
DynamicModule[{x},
Column[{Button["start",x=0; RunScheduledTask[x++;,{1,5}]],
Row[{Dynamic[HoldForm[x]]," = ", Dynamic[x, TrackedSymbols:>{x}]}]}]
]
Now it works.
A short remark on Istvan Zachar's solution. I think that his explanation is not quite correct. The kernel evaluation of a DynamicModule expression gives another DynamicModule kernel expression and that last one is displayed by the frontend. It can easily be found by converting the displayed DynamicModule (which belongs completely to the frontend) to InputForm. In the adapted example of Kuba it is (I left out the Button options):
DynamicModule[{x = 0},
Column[{Button[start, x = 0; RunScheduledTask[x++; , {1, 5}],
Row[{Dynamic[HoldForm[x]], " = ", Dynamic[x]}]},
ItemSize -> {Automatic, Automatic}],
DynamicModuleValues :> {}
]
The variable in the scheduled task is the local variable of the DynamicModule! There is no reason for using the Initialization option for entering the scheduled task. The crucial point is that the updating in Dynamic does not work, whether we implement the scheduled task in the Initialization option or not. Therefore, also in Istvan's solution the option TrackedSymbols in Dynamic is required.