This component provides a mechanism for progress-recording operations to notify interested parties of updates to progress.
The Progress class is designed to be the middle-man between an operation that wishes to report its progress and a listener that wishes to be notified of progress updates. Both of these parties will interface with each other through Progress class.
Suppose we have a Function that does some heavy calculation on a List, and wants to notify callers when it finishes one element, before it moves to the next:
public class HeavyCalculator implements Function<List<Integer>, List<Integer>> {
@Override
public List<Integer> apply(List<Integer> in) {
List<Integer> out = new ArrayList<>();
for(var i = 0; i < in.size(); i++) {
out.add(doTheComputation(in.get(i)));
}
return out;
}
}Firstly, we add the bookkeeping steps:
- Notify
Progressthat this Object wants to record its progress by callingProgress.register(Object progressible). This can either be called within ourFunction(by passingthis), or before theapplymethod is called (by passing ourHeavyCalculatorinstance). - Define what total progress means using
Progress.defineTotalProgress(int numStages, int numSubTasks). We make the distinction betweennumStagesandnumSubtasks:
numStagesletsProgressknow how many stages of computation will be performed within the current tasknumSubTasksletsProgressknow how many progress-reporting tasks will be called by the current task. Since they will report their own progress toProgress,Progresswill automatically update the progress of our current task as the subtasks progress.
- For each stage,
Progress.setStageMax(long max)must be called so thatProgressknows when each stage is completed, and moves onto the next. - Once the computation is complete, notify
Progressthat the task has completed by callingProgress.complete().
Once these bookkeeping stages are added, we can then call Progress.update. Our Function, updating progress, would then look like:
public class HeavyCalculator implements Function<List<Integer>, List<Integer>> {
@Override
public List<Integer> apply(List<Integer> in) {
Progress.register(this);
Progress.defineTotalProgress(1, 0);
Progress.setStageMax(in.size());
// compute
List<Integer> out = new ArrayList<>();
for(var i = 0; i < in.size(); i++) {
out.add(doTheComputation(in.get(i)));
Progress.update();
}
Progress.complete();
return out;
}
}Ops can also set their status through the Progress framework, using the method Progress.setStatus(String status). In these situations, out Function would not need to call Progress.defineTotalProgress() or Progress.setStageMax(). Progressible code is, of course, allowed to set both status and progress.
Progress is accessed by listeners using the Progress.addListener(Object progressible, Consumer<Task> l) method. This method must be called before progressible's code is executed, and all executions of progressible will then be sent to l.
l can be written as a lambda (shown below), or as an explicit implementation of the Consumer interface. The functional method, accept(Task t), is then called when any execution of progressible calls Progress.register, Progress.complete, Progress.update(), or Progress.setStatus(). Below is an example of how one might write a Consumer<Task> for HeavyCalculator:
HeavyCalculator calc = new HeavyCalculator();
// register a listener that prints progress to console
Progress.addListener(calc, t -> System.out.println(t.progress()));
// call HeavyCalculator
calc.apply(Arrays.asList(1, 2, 3, 4);