-
Notifications
You must be signed in to change notification settings - Fork 20
Tasks
In Java FX the UI runs in one thread, called the FXApplicationThread. In order to keep the UI fluent, work must be forked into the background. Usually in Correo work is triggered from the UI and will wait asynchronous for the results.
This is solved with tasks.
A task must be instantiated and ran afterwards.
This will look like this:
publishTaskFactory.create(getConnectionId(), messageDTO)
.onSuccess(this::onSuccess)
.onError(this::onError)
.run();
All tasks must implement the Task interface. There are some generics you should know about.
Generics | Description |
---|---|
<T> |
Type of result if the task succeeded. |
<P> |
Type of data that is send during progress. |
<E> |
Type of result in case of task failure. This is an enum often. |
Method name | Signature of Callback | Description |
---|---|---|
onStart(Callback) |
() -> void |
Will be triggered before the task will be executed. |
onProgress(Callback) |
(P) -> void |
Might be triggered never or multiple times, if the task supports progress. |
onSuccess(Callback) |
(T) -> void |
Will be called if the task succeeded without error |
onError(Callback) |
(TaskErrorResult<E>) -> void |
Will be called if the execution of the task produced an error. For more information see error handling. |
onFinally(Callback) |
() -> void |
Will be called, no matter if the task succeeded or failed. |
Note: Alle callbacks are automatically executed in the FXApplicationThread. So no need to run Platform.runLater
manually here.
Method name | Description |
---|---|
run() |
Executes the task. |
Each task automatically supports the future-like chain methods listed above. This is the preferred way to handle results. Nevertheless if your task produces events via EventBus you might want to listen to these instead using the future-like methods.
In every case it make sense to implement at least the onError
callback, in order to be able to give your users feedback in worst case.
The onError
callback is called with a TaskErrorResult<E>
. While E
is defined as the custom error object specific for the current task, the TaskErrorResult
contains more information.
Method name | Description |
---|---|
boolean isExpected() |
true if the error was expected and an error object with type E exists, otherwise false . |
E getExpectedError() |
Returns the expected error object if it exists. This is usally an enum. |
Throwable getUnexpectedError() |
Gives you the exception that was thrown unexpected. If you don't have custom expected error cases etc. you should at least implement a handling for the unexpected errors. Nevertheless if you don't do so. Correo will print a warning and catch those exceptions to display the message in an alert dialog. |
@Inject
public MyClass(PublishTaskFactory publishTaskFactory){
this.publishTaskFactory = publishTaskFactory;
}
@FXML
public void onButtonClick(){
publishTaskFactory.create(getConnectionId(), messageDTO)
.onSuccess(this::onSuccess)
.onProgress(this::onProgress)
.onError(this::onError)
.run();
}
private void onSuccess(MyResultDTO myResultDTO){
descriptionLabel.setText(myResultDTO.getStatusDescription());
}
private void onProgress(Integer status){
statusLabel.setText(status);
}
private void onError(TaskErrorResult<MyTask.Error> errorResult){
switch(errorResult.getExpectedError()){
case EMPTY -> AlertHelper.error("Empty message.");
case IOERROR -> AlertHelper.error("Unable to read file.");
default -> {}
}
if(errorResult.getUnexpectedError() != null){
AlertHelper.unexpectedError(errorResult.getUnexpectedError());
}
}
Whenever you do work it is required that you don't do this in the FXApplicationThread. Whenever possible use a task.
In order to achieve this it is quiet easy to create your own tasks.
Basically a task must implement the Task
interface. As this is only an interface there are different implementations for several use cases. The main method you must implement is the execute
method, which does the real work of the task. Everything else is optional.
Usually one does not need the full feature set.
A SimpleTask
is a task, that neither has a response type T
nor a specific error type E
nor does provide any sort of progress.
import org.correomqtt.business.concurrent.SimpleTask
class MyTask extends SimpleTask {
public void execute(){
// do something
}
}
Usally a task needs some input data. Those input data will be given into the constructor. So the example above will look like this:
import org.correomqtt.business.concurrent.SimpleResultTask
class MyTask extends SimpleTask {
private final String myParameter1;
private final Integer myParameter2
public MyTask(String myParameter1, Integer myParameter2){
this.myParameter1 = myParameter1;
this.myParameter2 = myParameter2;
}
public MyResultDTO execute(){
// do something with the attributes to produce a MyResultDTO
return myResultDTO;
}
}
If you do not have a result type, but different error cases: SimpleErrorTask
.
import org.correomqtt.business.concurrent.SimpleErrorTask
class MyTask extends SimpleErrorTask<MyTask.Error> {
public enum Error {
EMPTY,
IOERROR
}
private final String filename;
public MyTask(String filename) {
this.filename = filename;
}
public void execute(){
if(filename == null || filename.isEmpty()){
throw new TaskException(Error.EMPTY);
}
try {
// do something e.g. with the filesystem
} catch(IOException e){
// log the IOException
throw new TaskException(Error.IOERROR);
}
}
}
Maybe a rare use case, but if you only want to talk about progress and not about result or errors SimpleProgressTask
should be used.
import org.correomqtt.business.concurrent.SimpleProgressTask
class MyTask extends SimpleProgressTask<Integer> {
public MyTask(...){
...
}
public void execute(){
for(int step = 0; step < 100; step++){
// do the step, which might take some time
reportProgress(step);
}
}
}
While this is a simple example with just an Integer, the progess type P
might be more complex of course.
In the last chapter we saw different use cases of tasks. Providing results, producing progress and throw errors. Of course these can be mixed together.
import org.correomqtt.business.concurrent.NoProgressTask<MyResultDTO,MyTask.Error> {
class MyTask extends NoProgressTask<MyResultDTO,MyTask.Error> {
public enum Error {
EMPTY,
IOERROR
}
private final String filename;
public MyTask(String filename) {
this.filename = filename;
}
public MyResultDTO execute(){
if(filename == null || filename.isEmpty()){
throw new TaskException(Error.EMPTY);
}
try {
// do something e.g. with the filesystem
// produce a MyResultDTO
return myResultDTO;
} catch(IOException e){
// log the IOException
throw new TaskException(Error.IOERROR);
}
}
}
import org.correomqtt.business.concurrent.NoProgressTask<MyResultDTO,MyTask.Error> {
class MyTask extends NoProgressTask<MyResultDTO,Integer,MyTask.Error> {
public enum Error {
EMPTY,
IOERROR
}
private final String filename;
public MyTask(String filename) {
this.filename = filename;
}
public MyResultDTO execute(){
if(filename == null || filename.isEmpty()){
throw new TaskException(Error.EMPTY);
}
try {
// do something e.g. with the filesystem
// produce a MyResultDTO
for(int step = 0; step < 100; step++){
// do the step, which might take some time
reportProgress(step);
}
return myResultDTO;
} catch(IOException e){
// log the IOException
throw new TaskException(Error.IOERROR);
}
}
}
If you implement a task it is possible to hook into the task lifecycle by overriding methods.
class MyTask extends FullTask<T,P,E> {
...
protected void beforeHook() {
// Called before everything is started
}
protected void successHook(T result) {
// Called after the execute method returned without error
}
protected void errorHook(TaskErrorResult<E> errorResult) {
// Called if an error occured during execution.
// This will contain expected errors as well as unexpected exceptions
// In case of tasks without custom errors, the error result is a SimpleTaskErrorResult,
// which is not generic and does contain an exception only.
}
protected void finalHook() {
// Called after the execution finished, not matter if with error or with success.
}
...
}
QUICK NAV
For users
For developers