Skip to content

Commit

Permalink
Merge pull request androidannotations#569 from rom1v/cancel_background
Browse files Browse the repository at this point in the history
Cancel background tasks
  • Loading branch information
DayS committed Jun 9, 2013
2 parents 2323d7a + 4161c62 commit 77cbcea
Show file tree
Hide file tree
Showing 6 changed files with 574 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,26 @@
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Background {
long delay() default 0;
/**
* Identifier for task cancellation.
*
* To cancel all tasks having a specified background id:
*
* <pre>
* boolean mayInterruptIfRunning = true;
* BackgroundExecutor.cancelAll(&quot;my_background_id&quot;, mayInterruptIfRunning);
* </pre>
**/
String id() default "";

/** Minimum delay, in milliseconds, before the background task is executed. */
int delay() default 0;

/**
* Serial execution group.
*
* All background tasks having the same <code>serial</code> will be executed
* sequentially.
**/
String serial() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,291 @@
*/
package org.androidannotations.api;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import android.util.Log;

public class BackgroundExecutor {

private static Executor executor = Executors.newCachedThreadPool();
private static ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors());
private static final String TAG = "BackgroundExecutor";

private static Executor executor = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors());

private static final List<Task> tasks = new ArrayList<Task>();

/**
* Execute a runnable after the given delay.
*
* @param runnable
* the task to execute
* @param delay
* the time from now to delay execution, in milliseconds
* @throws IllegalArgumentException
* if <code>delay</code> is strictly positive and the current
* executor does not support scheduling (if
* {@link #setExecutor(Executor)} has been called with such an
* executor)
* @return Future associated to the running task
*/
private static Future<?> directExecute(Runnable runnable, int delay) {
Future<?> future = null;
if (delay > 0) {
/* no serial, but a delay: schedule the task */
if (!(executor instanceof ScheduledExecutorService)) {
throw new IllegalArgumentException("The executor set does not support scheduling");
}
ScheduledExecutorService scheduledExecutorService = (ScheduledExecutorService) executor;
future = scheduledExecutorService.schedule(runnable, delay, TimeUnit.MILLISECONDS);
} else {
if (executor instanceof ExecutorService) {
ExecutorService executorService = (ExecutorService) executor;
future = executorService.submit(runnable);
} else {
/* non-cancellable task */
executor.execute(runnable);
}
}
return future;
}

/**
* Execute a task after (at least) its delay <strong>and</strong> after all
* tasks added with the same non-null <code>serial</code> (if any) have
* completed execution.
*
* @param task
* the task to execute
* @throws IllegalArgumentException
* if <code>task.delay</code> is strictly positive and the
* current executor does not support scheduling (if
* {@link #setExecutor(Executor)} has been called with such an
* executor)
*/
public static synchronized void execute(Task task) {
Future<?> future = null;
if (task.serial == null || !hasSerialRunning(task.serial)) {
task.executionAsked = true;
future = directExecute(task, task.remainingDelay);
}
if (task.id != null || task.serial != null) {
/* keep task */
task.future = future;
tasks.add(task);
}
}

/**
* Execute a task.
*
* @param runnable
* the task to execute
* @param id
* identifier used for task cancellation
* @param delay
* the time from now to delay execution, in milliseconds
* @param serial
* the serial queue (<code>null</code> or <code>""</code> for no
* serial execution)
* @throws IllegalArgumentException
* if <code>delay</code> is strictly positive and the current
* executor does not support scheduling (if
* {@link #setExecutor(Executor)} has been called with such an
* executor)
*/
public static void execute(final Runnable runnable, String id, int delay, String serial) {
execute(new Task(id, delay, serial) {
@Override
public void execute() {
runnable.run();
}
});
}

/**
* Execute a task after the given delay.
*
* @param runnable
* the task to execute
* @param delay
* the time from now to delay execution, in milliseconds
* @throws IllegalArgumentException
* if <code>delay</code> is strictly positive and the current
* executor does not support scheduling (if
* {@link #setExecutor(Executor)} has been called with such an
* executor)
*/
public static void execute(Runnable runnable, int delay) {
directExecute(runnable, delay);
}

/**
* Execute a task.
*
* @param runnable
* the task to execute
*/
public static void execute(Runnable runnable) {
executor.execute(runnable);
directExecute(runnable, 0);
}

/**
* Execute a task after all tasks added with the same non-null
* <code>serial</code> (if any) have completed execution.
*
* Equivalent to {@link #execute(Runnable, String, int, String)
* execute(runnable, id, 0, serial)}.
*
* @param runnable
* the task to execute
* @param id
* identifier used for task cancellation
* @param serial
* the serial queue to use (<code>null</code> or <code>""</code>
* for no serial execution)
*/
public static void execute(Runnable runnable, String id, String serial) {
execute(runnable, id, 0, serial);
}

/**
* Change the executor.
*
* Note that if the given executor is not a {@link ScheduledExecutorService}
* then executing a task after a delay will not be supported anymore. If it
* is not even a {@link ExecutorService} then tasks will not be cancellable
* anymore.
*
* @param executor
* the new executor
*/
public static void setExecutor(Executor executor) {
BackgroundExecutor.executor = executor;
}

public static void executeDelayed(Runnable runnable, long delay) {
scheduledExecutor.schedule(runnable, delay, TimeUnit.MILLISECONDS);
/**
* Cancel all tasks having the specified <code>id</code>.
*
* @param id
* the cancellation identifier
* @param mayInterruptIfRunning
* <code>true</code> if the thread executing this task should be
* interrupted; otherwise, in-progress tasks are allowed to
* complete
*/
public static synchronized void cancelAll(String id, boolean mayInterruptIfRunning) {
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
if (id.equals(task.id)) {
tasks.remove(i);
if (task.future != null) {
task.future.cancel(mayInterruptIfRunning);
} else if (task.executionAsked) {
Log.w(TAG, "A task with id " + task.id + " cannot be cancelled (the executor set does not support it)");
}
}
}
}

public static void setScheduledExecutor(ScheduledExecutorService scheduledExecutor) {
BackgroundExecutor.scheduledExecutor = scheduledExecutor;
/**
* Indicates whether a task with the specified <code>serial</code> has been
* submitted to the executor.
*
* @param serial
* the serial queue
* @return <code>true</code> if such a task has been submitted,
* <code>false</code> otherwise
*/
private static boolean hasSerialRunning(String serial) {
for (Task task : tasks) {
if (task.executionAsked && serial.equals(task.serial)) {
return true;
}
}
return false;
}

/**
* Retrieve and remove the first task having the specified
* <code>serial</code> (if any).
*
* @param serial
* the serial queue
* @return task if found, <code>null</code> otherwise
*/
private static Task take(String serial) {
int len = tasks.size();
for (int i = 0; i < len; i++) {
if (serial.equals(tasks.get(i).serial)) {
return tasks.remove(i);
}
}
return null;
}

public static abstract class Task implements Runnable {

private String id;
private int remainingDelay;
private long targetTimeMillis; /* since epoch */
private String serial;
private boolean executionAsked;
private Future<?> future;

public Task(String id, int delay, String serial) {
if (!"".equals(id)) {
this.id = id;
}
if (delay > 0) {
remainingDelay = delay;
targetTimeMillis = System.currentTimeMillis() + delay;
}
if (!"".equals(serial)) {
this.serial = serial;
}
}

@Override
public void run() {
try {
execute();
} finally {
/* handle next tasks */
postExecute();
}
}

public abstract void execute();

private void postExecute() {
if (id == null && serial == null) {
/* nothing to do */
return;
}
synchronized (BackgroundExecutor.class) {
/* execution complete */
tasks.remove(this);

if (serial != null) {
Task next = take(serial);
if (next != null) {
if (next.remainingDelay != 0) {
/* the delay may not have elapsed yet */
next.remainingDelay = Math.max(0, (int) (targetTimeMillis - System.currentTimeMillis()));
}
/* a task having the same serial was queued, execute it */
BackgroundExecutor.execute(next);
}
}
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,20 @@ public String getIdStringFromIdFieldRef(JFieldRef idRef) {
throw new IllegalStateException("Unable to extract target name from JFieldRef");
}

public JTryBlock surroundWithTryCatch(EBeanHolder holder, JBlock block, JBlock content, String exceptionMessage) {
Classes classes = holder.classes();
JTryBlock tryBlock = block._try();
tryBlock.body().add(content);
JCatchBlock catchBlock = tryBlock._catch(classes.RUNTIME_EXCEPTION);
JVar exceptionParam = catchBlock.param("e");
JInvocation errorInvoke = classes.LOG.staticInvoke("e");
errorInvoke.arg(holder.generatedClass.name());
errorInvoke.arg(exceptionMessage);
errorInvoke.arg(exceptionParam);
catchBlock.body().add(errorInvoke);
return tryBlock;
}

public JDefinedClass createDelegatingAnonymousRunnableClass(EBeanHolder holder, JMethod delegatedMethod) {

JCodeModel codeModel = holder.codeModel();
Expand All @@ -242,20 +256,9 @@ public JDefinedClass createDelegatingAnonymousRunnableClass(EBeanHolder holder,
runMethod.annotate(Override.class);

JBlock runMethodBody = runMethod.body();
JTryBlock runTry = runMethodBody._try();

runTry.body().add(previousMethodBody);

JCatchBlock runCatch = runTry._catch(classes.RUNTIME_EXCEPTION);
JVar exceptionParam = runCatch.param("e");

JInvocation errorInvoke = classes.LOG.staticInvoke("e");

errorInvoke.arg(holder.generatedClass.name());
errorInvoke.arg("A runtime exception was thrown while executing code in a runnable");
errorInvoke.arg(exceptionParam);
surroundWithTryCatch(holder, runMethodBody, previousMethodBody, "A runtime exception was thrown while executing code in a runnable");

runCatch.body().add(errorInvoke);
return anonymousRunnableClass;
}

Expand Down

0 comments on commit 77cbcea

Please sign in to comment.