7

I have a series of tasks (i.e. Runnables) to be executed by an Executor.
Each task requires a certain condition to be valid in order to proceed. I would be interested to know if there is a way to somehow configure Executor to move tasks in the end of the queue and try to execute them later when the condition would be valid and the task be able to execute and finish.
So the behavior be something like:

  1. Thread-1 take tasks from queue and run is called
  2. Inside run the condition is not yet valid
  3. Task stops and Thread-1 places task in the end of the queue and gets next task to execute
  4. Later on Thread-X (from thread pool) picks task again from queue condition is valid and task is being executed
Cratylus
  • 52,998
  • 69
  • 209
  • 339
  • Just create a new task that is equal to current one and put it to the queue. Finish current task – hoaz Nov 20 '12 at 21:06
  • @hoaz:You mean reference the executor from inside the tasks? – Cratylus Nov 20 '12 at 21:09
  • 1
    @Cratylus - yes, if the task has a reference back the the executor, it could re-queue itself. just make sure that the executor's pending task queue is unbounded or you can end up dead-locking yourself. – jtahlborn Nov 20 '12 at 21:21

3 Answers3

3

Create the executor first.

You have several possibilites.

If I suppose that your tasks implement a simple interface to query their status (something like an enum with 'NeedReschedule' or 'Completed'), then implement a wrapper (implementing Runnable) for your tasks which will take the task and the executor as instanciation parameters. This wrapper will run the task it is bound to, check its status afterwards, and if necessary reschedule a copy of itself in the executor before terminating.

Alternatively, you could use an execption mechanism to signal the wrapper that the task must be rescheduled. This solution is simpler, in the sense that it doesn't require a particular interface for you task, so that simple Runnable could be thrown in the system without trouble. However, exceptions incur more computation time (object construction, stack trace etc.).

Here's a possible implementation of the wrapper using the exception signaling mechanism. You need to implement the RescheduleException class extending Throwable, which may be fired by the wrapped runnable (no need for a more specific interface for the task in this setup). You could also use a simple RuntimeException as proposed in another answer, but you will have to test the message string to know if this is the exception you are waiting for.

 public class TaskWrapper implements Runnable {

    private final ExecutorService executor;
    private final Runnable task;       

    public TaskWrapper(ExecutorService e, Runnable t){
         executor = e;
         task = t;
    }

@Override
public void run() {

    try {
               task.run();
    } 
    catch (RescheduleException e) {
        executor.execute(this);
    }
}

Here's a very simple application firing up 200 wrapped tasks randomly asking a reschedule.

class Task implements Runnable {

  @Override
  public void run(){
     if (Maths.random() > 0.5)
      throw new RescheduleException();
   }     
}


public class Main {

public static void main(String[] args){

    ExecutorService executor = Executors.newFixedThreadPool(10);

    int i = 200;
            while(i--)
       executor.execute(new TaskWrapper(executor, new Task());
}
}

You could also have a dedicated thread to monitor the other threads results (using a message queue) and reschedule if necessary, but you lose one thread, compared to the other solution.

didierc
  • 14,572
  • 3
  • 32
  • 52
  • Could you please provide a little more details on your suggestions? – Cratylus Nov 20 '12 at 21:32
  • `This wrapper will run the task it is bound to, check its status afterwards`.How?Using `Callable`s? – Cratylus Nov 20 '12 at 21:58
  • I came up with the exception mechanism as I wrote the code, seems cleaner. – didierc Nov 20 '12 at 22:18
  • The wrapper parameter could even be a simple Executor in my inplementation. – didierc Nov 20 '12 at 22:20
  • `RescheduleException` is my class?Isn't there a more suitable existing exception? – Cratylus Nov 21 '12 at 20:36
  • Also the way I see it, with the code you posted the rescheduled task will/could be executed immediatelly as you resbmit on reschedule.It would be better to delay execution – Cratylus Nov 21 '12 at 20:37
  • indeed, having a small delay could be desirable. Note that you don't define the circumstancez of a rescheduling, and perhaps it would be better for some tasks to be put on hold and define an event to be waited for, which would trigger the activation of the task. – didierc Nov 21 '12 at 20:45
3

In Java 6, the ThreadPoolExecutor constructor takes a BlockingQueue<Runnable>, which is used to store the queued tasks. You can implement such a blocking queue which overrides the poll() so that if an attempt is made to remove and execute a "ready" job, then poll proceeds as normal. Otherwise the runnable is place at the back of the queue and you attempt to poll again, possibly after a short timeout.

mbatchkarov
  • 15,487
  • 9
  • 60
  • 79
  • Doesn't this depend on implementation detail?How do I know for sure that the implementation will use *poll*.I don't want it to be based on `Oracle` source code for example – Cratylus Nov 20 '12 at 21:31
  • I don't know if it use `poll` for sure. I know it would use one of `remove()`, `poll()`, take() or `poll(time, unit)`, because these are the methods for removing from a blocking queue as per the documentation. – mbatchkarov Nov 20 '12 at 21:50
  • Besides that, although your suggestion is appealing (+1), I can't see how it will help here.The condition under check is not some global condition, it is part of the state of the `Runnable` during `run` – Cratylus Nov 20 '12 at 21:51
  • The question does not say anything about the condition :) – mbatchkarov Nov 20 '12 at 21:52
  • It is not a race condition.It is a logical condition (`if(...)`). – Cratylus Nov 20 '12 at 21:54
  • I meant you didn't specify if the condition is local to the Runnable or more global – mbatchkarov Nov 21 '12 at 10:31
  • It is local to the `Runnable` – Cratylus Nov 21 '12 at 20:33
3

Unless you have to have busy waiting, you can add a repeating task to a ScheduledExecutorService with an appropriate polling interval which you cancel or kill after it is "valid" to run.

ScheduleExecutorService ses = ...

ses.scheduleAtFixedRate(new Runnable() {
    public void run() {
        if (!isValid()) return;
        preformTask();
        throw new RuntimeException("Last run");
    }
}, PERIOD, PERIOD, TimeUnit.MILLI_SECONDS);
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • What is the `RuntimeException` for? – Cratylus Nov 20 '12 at 21:53
  • Its the simplest way to stop the task repeating. There are alternatives but they are at least as ugly and more complex. – Peter Lawrey Nov 20 '12 at 21:56
  • The exception will not affect next tasks in the queue, right? – Cratylus Nov 20 '12 at 21:57
  • The ExecutorService is designed to capture an Exception or Error and store in the `Future` object returned. (discarded in this example) – Peter Lawrey Nov 21 '12 at 09:19
  • I like this solution, much cleaner than mine, but how do you discriminate real runtime errors from the signaling ones? – didierc Nov 21 '12 at 21:08
  • @PeterLawrey:Would doing: `ses.scheduleAtFixedRate(this, PERIOD, PERIOD, TimeUnit.MILLI_SECONDS);` from inside a scheduled runnable cause any issue? – Cratylus Nov 22 '12 at 20:35
  • Every time you call this you add a new recurring job. You don't want to create an exponential increase in the number of jobs to be run so I don't think it would be very useful. – Peter Lawrey Nov 23 '12 at 14:14