Tasks recipe is a concept to execute DAG of Runnable instances using
a state machine. This recipe has been developed from ideas introduced
in sample Chapter 35, Tasks.
Generic concept of a state machine is shown below. In this state chart
everything under TASKS just shows a generic concept of how a single
task is executed. Because this recipe allows to register deep
hierarcical DAG of tasks, meaning a real state chart would be deep
nested collection of sub-states and regions, there’s no need to be
more presise.
For example if you have only two registered tasks, below state chart
would be correct with TASK_id replaced with TASK_1 and TASK_2 if
registered tasks ids are 1 and 2.

Executing a Runnable may result an error and especially if a complex
DAG of tasks is involved it is desirable that there is a way to handle
tasks execution errors and then having a way to continue execution
without executing already successfully executed tasks. Addition to
this it would be nice if some execution errors can be handled
automatically and as a last fallback, if error can’t be handled
automatically, state machine is put into a state where user can handle
errors manually.
TasksHandler contains a builder method to configure handler instance
and follows a simple builder patter. This builder can be used to
register Runnable tasks, TasksListener instances, define
StateMachinePersist hook, and setup custom TaskExecutor instance.
Now lets take a simple Runnable just doing a simple sleep as shown
below. This is a base of all examples in this chapter.
private Runnable sleepRunnable() { return new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { } } }; }
To execute multiple sleepRunnable tasks just register tasks and
execute runTasks() method from TasksHandler.
TasksHandler handler = TasksHandler.builder()
.task("1", sleepRunnable())
.task("2", sleepRunnable())
.task("3", sleepRunnable())
.build();
handler.runTasks();
Order to listen what is happening with a task execution an instance of
a TasksListener can be registered with a TasksHandler. Recipe
provides an adapter TasksListenerAdapter if you don’t want to
implement a full interface. Listener provides a various hooks to
listen tasks execution events.
private class MyTasksListener extends TasksListenerAdapter { @Override public void onTasksStarted() { } @Override public void onTasksContinue() { } @Override public void onTaskPreExecute(Object id) { } @Override public void onTaskPostExecute(Object id) { } @Override public void onTaskFailed(Object id, Exception exception) { } @Override public void onTaskSuccess(Object id) { } @Override public void onTasksSuccess() { } @Override public void onTasksError() { } @Override public void onTasksAutomaticFix(TasksHandler handler, StateContext<String, String> context) { } }
Listeners can be either registered via a builder or directly with a
TasksHandler as shown above.
MyTasksListener listener1 = new MyTasksListener(); MyTasksListener listener2 = new MyTasksListener(); TasksHandler handler = TasksHandler.builder() .task("1", sleepRunnable()) .task("2", sleepRunnable()) .task("3", sleepRunnable()) .listener(listener1) .build(); handler.addTasksListener(listener2); handler.removeTasksListener(listener2); handler.runTasks();
Above sample show how to create a deep nested DAG of tasks. Every task needs to have an unique identifier and optionally as task can be defined to be a sub-task. Effectively this will create a DAG of tasks.
TasksHandler handler = TasksHandler.builder()
.task("1", sleepRunnable())
.task("1", "12", sleepRunnable())
.task("1", "13", sleepRunnable())
.task("2", sleepRunnable())
.task("2", "22", sleepRunnable())
.task("2", "23", sleepRunnable())
.task("3", sleepRunnable())
.task("3", "32", sleepRunnable())
.task("3", "33", sleepRunnable())
.build();
handler.runTasks();
When error happens and a state machine running these tasks goes into a
ERROR state, user can call handler methods fixCurrentProblems to
reset current state of tasks kept in a state machine extended state
variables. Handler method continueFromError can then be used to
instruct state machine to transition from ERROR state back to
READY state where tasks can be executed again.
TasksHandler handler = TasksHandler.builder()
.task("1", sleepRunnable())
.task("2", sleepRunnable())
.task("3", sleepRunnable())
.build();
handler.runTasks();
handler.fixCurrentProblems();
handler.continueFromError();