Skip to content

Instantly share code, notes, and snippets.

@mpilone
Last active January 30, 2019 16:36
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mpilone/6156009 to your computer and use it in GitHub Desktop.
Save mpilone/6156009 to your computer and use it in GitHub Desktop.
A background task that can be executed to perform background work and safe UI updates in Vaadin using the v7.1 UI.access() method. Currently you implement it by extension but it may make more sense to provide an interface for the doWork and doUiUpdate methods similar to the relationship between Thread and Runnable.
package org.mpilone.vaadin;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import com.vaadin.server.VaadinSession;
import com.vaadin.ui.UI;
import com.vaadin.ui.UIDetachedException;
/**
* A background task that handles synchronizing to the UI when performing
* updates from a background thread. The common use case is to create a
* background task, add it to a thread pool and then update the UI in the safe,
* {@link #doUiUpdate(Throwable)} method.
*
* @author mpilone
*/
public abstract class BackgroundUiTask implements RunnableFuture<Void> {
/**
* Called when a lock has been obtained on the UI's {@link VaadinSession} and
* it is safe to apply UI updates. This method will only be called after
* {@link #doWork()} is complete and the task hasn't been cancelled. The
* default implementation calls {@link #doUiUpdate()}. This method will be
* called even if {@link #doWork()} throws an exception to give the task a
* chance to cleanup the UI.
*
* @param ex
* the exception raised in {@link #doWork()} or null if no exception
* was raised
*/
protected void doUiUpdate(Throwable ex) {
doUiUpdate();
}
/**
* A convenience method that will be called by {@link #doUiUpdate(Throwable)}.
* The default implementation does nothing but subclasses can override this
* method if the value of the exception is not important.
*/
protected void doUiUpdate() {
}
/**
* Called when the task is started. All time consuming work should be done in
* this method. No UI updates are permitted in this method because it is not
* synchronized to the {@link VaadinServiceSession}. Good implementations
* should attempt to stop if the task is cancelled while processing.
*/
protected abstract void doWork();
/**
* A simple runnable that executes the first of two parts of a
* {@link BackgroundUiTask}. The {@link BackgroundUiTask#doWork()} method will
* be called and the {@link BackgroundUiTask#doneLatch} will be decremented.
*
* @author mpilone
*/
private class DoWorkRunnable implements Runnable {
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
try {
if (ui == null) {
throw new UIDetachedException("No UI available for "
+ "background synchronization.");
}
doWork();
}
finally {
doneLatch.countDown();
}
}
}
/**
* A simple runnable that executes the second of two parts of a
* {@link BackgroundUiTask}. The
* {@link BackgroundUiTask#doUiUpdate(Throwable)} method will be called and
* the {@link BackgroundUiTask#doneLatch} will be decremented.
*
* @author mpilone
*/
private class DoUiUpdateRunnable implements Runnable {
private Throwable exception;
public DoUiUpdateRunnable(Throwable exception) {
this.exception = exception;
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
try {
if (ui == null) {
throw new UIDetachedException("No UI available for "
+ "background synchronization.");
}
doUiUpdate(exception);
}
finally {
doneLatch.countDown();
}
}
}
/**
* The mutex for swapping futures to ensure that the active future is safely
* accessed during all delegated calls.
*/
private final Object FUTURE_MUTEX = new Object();
/**
* The active future that is running/will be run. When the future is complete,
* it must count down the {@link #doneLatch}.
*/
private Future<Void> future;
/**
* The count down latch used to keep track of the number of futures that need
* to execute before this task is considered complete. Calls to {@link #get()}
* will block on this latch until it reaches 0.
*/
private CountDownLatch doneLatch = new CountDownLatch(2);
/**
* The UI to synchronize with when updating from a background thread.
*/
private UI ui;
/**
* Constructs the background task which will synchronized with the UI
* available at {@link UI#getCurrent()}. The task must be initially
* constructed in the main thread where the UI is available.
*/
public BackgroundUiTask() {
this(UI.getCurrent());
}
/**
* Constructs the background task which will synchronized with the given UI.
*/
public BackgroundUiTask(UI ui) {
this.ui = ui;
// We wrap the runnable in a future task to get a common API to which to
// delegate. The task also handles all the tricky exception handling and
// thread safe cancellation.
this.future = new FutureTask<Void>(new DoWorkRunnable(), null);
}
/*
* (non-Javadoc)
*
* @see java.util.concurrent.Future#cancel(boolean)
*/
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
synchronized (FUTURE_MUTEX) {
return future.cancel(mayInterruptIfRunning);
}
}
/**
* A convenience method for {@link #cancel(boolean)} with a value of false.
*/
public void cancel() {
cancel(false);
}
/*
* (non-Javadoc)
*
* @see java.util.concurrent.Future#get()
*/
@Override
public Void get() throws InterruptedException, ExecutionException {
doneLatch.await();
return future.get();
}
/*
* (non-Javadoc)
*
* @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)
*/
@Override
public Void get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
if (doneLatch.await(timeout, unit)) {
return future.get(timeout, unit);
}
else {
throw new TimeoutException();
}
}
/*
* (non-Javadoc)
*
* @see java.util.concurrent.Future#isCancelled()
*/
@Override
public boolean isCancelled() {
synchronized (FUTURE_MUTEX) {
return future.isCancelled();
}
}
/*
* (non-Javadoc)
*
* @see java.util.concurrent.Future#isDone()
*/
@Override
public boolean isDone() {
return doneLatch.getCount() == 0 && future.isDone();
}
/*
* (non-Javadoc)
*
* @see java.util.concurrent.RunnableFuture#run()
*/
@Override
public void run() {
// Run the initial future, the doWork runnable.
((FutureTask<Void>) future).run();
synchronized (FUTURE_MUTEX) {
Throwable exception = null;
try {
// Check if we got an exception during execution.
future.get();
}
catch (Throwable ex) {
exception = ex;
}
if (future.isCancelled()) {
// Cancelled during doWork so we'll skip doUiUpdate and simply count
// down.
doneLatch.countDown();
}
else {
// Fire up doUiUpdate runnable which gets us the second future of the
// task.
future = ui.access(new DoUiUpdateRunnable(exception));
}
}
}
}
@ldelaprade
Copy link

Tried to use this for coding a VAADIN progress window.
This progress window started the task when it got focus:

class MyProgressWindow extends Window implements FocusListener, ClickListener
...
...
@OverRide
public void focus(FocusEvent event)
{
// processWasStarted flag to protect against multiple 'OnFocus' events
if( !processWasStarted )
{
try
{
processWasStarted = true;
JetBackgroundUiTask task = new JetBackgroundUiTask()
{
...
...
task.run();

Unfortunately, when the task is running, The clic on the Cancel button never reaches the main thread.
Consequently user could never cancel the long running background thread

@archenroot
Copy link

Hm, any update on this?

I am also now facing how to preperly design long running task, I implement microservices with applied Saga and Audit patterns -> Event Sourcing and CQRS enabled. As message broker is used as master for service layer, I am going to use it for Vaadin as well. So when starting the background I just put message into queue and start thread to listen on reply/response/progress queue, such thread has logic to operate without background need:

  • progress events are consumed only until final response of either success or failure is received
  • progress messages interval configuration on service layer is set to 5 seconds, so on Vaadin client if no message is received in about 7 seconds - it means failure

To me background tasks are not meant anything to Vaadin or UI, this is for UI simply microservice somewhere in the wild and I am using message broker as message highway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment