Progress monitor 🔗
When a library function is executed in a Wolfram Language session, the Kernel will wait until the function returns. There is usually no real-time
feedback about function progress. LibraryLink offers the AbortQ
function which allows developers to correctly handle cases when the user aborts a library function execution. The drawback is that it is entirely up to library
developers to use AbortQ
manually. The library must still return even when AbortQ
returns true
, so there is still no guarantee that the execution
will end immediately upon an abort.
In practice, using time-consuming, non-abortable library functions often looks like this:
LLU provides a class LLU::ProgressMonitor
which uses a 1-element shared tensor to report progress to Wolfram Language during
library function execution. The value in the tensor is a real number between 0.0 and 1.0 which indicates current progress of the function. It can be
increased/decreased by a given step (using convenient increment/decrement operators) or set to arbitrary value.
(Yes, decreasing progress may be useful sometimes too.)
Progress value can be read in the Kernel and displayed in the Front End for example as a progress bar.
As with many LLU features, the ProgressMonitor
implementation consists of two parts: one in the library and one in Wolfram Language code. The goal is
to have decent functionality with minimal effort on the programmer’s side.
In C++, the only thing you have to do is to get an instance of ProgressMonitor from MArgumentManager.
auto pm = mngr.getProgressMonitor();
ProgressMonitor
class also defines a method LLU::ProgressMonitor::checkAbort()
which checks if the user has requested to abort current computation
and if so, throws an exception. It’s a static function, so even if you don’t own a ProgressMonitor
instance, you can still check for aborts.
Calling checkAbort()
also has a significant side-effect: it gives some CPU time to the Kernel in the middle of library function evaluation
and this may be helpful in updating the Dynamic
which moves the progress bar in Front End.
The Wolfram Language implementation is minimal and basic. When you load an LLU function which uses ProgressMonitor
with PacletFunctionSet
or similar,
you have to pass an option “ProgressMonitor” -> x
, where x
is a Symbol.
Every time your library function reports progress, the new progress value will be assigned to x
. If and how the progress is visualized
in the notebook is completely up to the developer. Using a ProgressIndicator
or ComputeWithProgress
from GeneralUtilities
is recommended.
The final effect may look like this (the second function call is aborted with a keyboard shortcut (by default Alt+. on Linux)):
Example 🔗
Consider a simple function that just sleeps in a loop moving the progress bar in a steady pace. This function takes two arguments:
(Real) Total time (in seconds) for the function to complete
(ProgressMonitor) Shared instance of an MTensor automatically wrapped in ProgressMonitor by MArgumentManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | EXTERN_C DLLEXPORT int UniformProgress(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res) {
// Create MArgumentManager to manage all the input and output arguments for the library function
LLU::MArgumentManager mngr(libData, Argc, Args, Res);
// Get first argument which determines how many seconds should this function take to evaluate
auto totalTime = mngr.getReal(0);
// Calculate number of steps for the progress bar, we want 10 steps per second
auto numOfSteps = static_cast<int>(std::ceil(totalTime * 10));
// Get ProgressMonitor instance, initialize with the number of seconds per step
auto pm = mngr.getProgressMonitor(1.0 / numOfSteps);
// Sleep in a loop, increase progress in each iteration. Increasing progress also automatically checks for Abort.
for (int i = 0; i < numOfSteps; ++i) {
std::this_thread::sleep_for(100ms);
++pm;
}
// Set function result and return
mngr.setInteger(42);
return LLU::ErrorCode::NoError;
}
|
For progress reporting to work on the Wolfram Language side as expected, the library function must be loaded with extra option “ProgressMonitor”, like this:
`LLU`PacletFunctionSet`[UniformProgress, "UniformProgress", {Real}, Integer, "ProgressMonitor" -> MyPaclet`PM`UniformProgress];
By default, “ProgressMonitor” -> None
is used.
It’s a good idea to make sure the name for the monitoring symbol will be unique. One suggestion is to use PacletName`PM
as the context, and the name of the
symbol to be the same as the function name.
Now, run your library function with simple progress bar:
Monitor[
UniformProgress[5],
ProgressIndicator[Dynamic @ First @ Refresh[MyPaclet`PM`UniformProgress, UpdateInterval -> 0.2]]
]
API reference 🔗
-
class
LLU
::
ProgressMonitor
🔗 Stores and updates current progress of computation in a location shared between the library and WL Kernel.
ProgressMonitor receives an instance of a shared Tensor<double> in constructor and becomes the (shared) owner. Progress is a single number of type
double
between 0. and 1. This class offers an interface for modifying the progress value (increase/decrease by a given step or set to an arbitrary value) and one static function for checking if a user requested to abort the computation in WL Kernel.Public Types
A type to represent a buffer shared between LLU and the Kernel which is used to report progress.
Public Functions
Construct a new ProgressMonitor.
-
ProgressMonitor
(const ProgressMonitor&) = delete 🔗 Copy-constructor is disabled because ProgressMonitor shares a Tensor with WL Kernel.
-
ProgressMonitor &
operator=
(const ProgressMonitor&) = delete 🔗 Copy-assignment is disabled because ProgressMonitor shares a Tensor with WL Kernel.
-
ProgressMonitor
(ProgressMonitor&&) = default 🔗 Default move-constructor.
-
ProgressMonitor &
operator=
(ProgressMonitor&&) = default 🔗 Default move-assignment operator.
-
~ProgressMonitor
() = default 🔗 Default destructor.
-
double
get
() const 🔗 Get current value of the progress.
-
void
set
(double progressValue) 🔗 Set current progress value.
-
double
getStep
() const 🔗 Get current step value.
-
void
setStep
(double stepValue) 🔗 Change step value to a given number.
-
ProgressMonitor &
operator++
() 🔗 Increment current progress value by
step
.
-
ProgressMonitor &
operator+=
(double progress) 🔗 Increment current progress value by a given number.
-
ProgressMonitor &
operator--
() 🔗 Decrement current progress value by
step
.
-
ProgressMonitor &
operator-=
(double progress) 🔗 Decrement current progress value by a given number.
Public Static Functions
-
void
checkAbort
() 🔗 Check whether user requested to abort the computation in WL Kernel.
-
constexpr double
getDefaultStep
() noexcept 🔗 Return default step for the ProgressMonitor.