8

I apologise for the length of this problem, but I thought it important to include sufficient detail given that I'm looking for a suitable approach to my problem, rather than a simple code suggestion!


General description:

I am working on a project that requires tasks being able to be 'scheduled' at some relative repeating interval.

These intervals are in terms of some internal time, that is represented as an integer that is incremented as the program executes (so not equal to real time). Each time this happens, the schedule will be interogated to check for any tasks due to execute at this timestep.

If a task is executed, it should then be rescheduled to run again at a position relative to the current time (e.g. in 5 timesteps). This relative position is simply stored as an integer property of the Task object.

The problem:

I am struggling somewhat to decide upon how I should structure this- partly because it is a slightly difficult set of search terms to look for.

As it stands, I am thinking that each time the timer is incremented I need to:

  1. Execute tasks at the '0' position in the schedule
  2. Re-add those tasks to the schedule again at their relative position (e.g. a task that repeats every 5 steps will be returned to the position 5)
  3. Each group of tasks in the schedule will have their 'time until execution' decremented one (e.g. a task at position 1 will move to position 0)

Assumptions:

There are a couple of assumptions that may limit the possible solutions I can use:

  • The interval must be relative, not a specific time, and is defined to be an integer number of steps from the current time
  • These intervals may take any integer value, e.g. are not bounded.
  • Multiple tasks may be scheduled for the same timestep, but their order of execution is not important
  • All execution should remain in a single thread- multi-threaded solutions are not suitable due to other constraints

The main questions I have are:

How could I design this Schedule to work in an efficient manner? What datatypes/collections may be useful?

Is there another structure/approach I should consider?

Am I wrong to dismiss scheduling frameworks (e.g. Quartz), which appear to work more in the 'real' time domain rather 'non-real' time domain?


Many thanks for any possible help. Please feel free to comment for further information if neccessary, I will edit wherever needed!

obfuscation
  • 1,023
  • 3
  • 16
  • 23
  • It seems to me that you want to simulate time in your application via a counter.I was wondering why you do not want to use system time i.e. real time? – Cratylus Sep 09 '11 at 14:20
  • Surely you can achieve this with quartz have you used Quartz before. – Java Ka Baby Sep 09 '11 at 14:21
  • @user384706 - I'm working in a simulated situation whereby each timestep may take very different lengths of time, and therefore is represented as a counter. – obfuscation Sep 09 '11 at 14:26
  • @Java Ja Baby - I haven't used Quartz before, but from what I'd heard it specialised more in scheduling in 'real' time (as I said in OP). Is this belief misplaced? – obfuscation Sep 09 '11 at 14:27
  • I understand you have a specific problem, and do not want to be a "pain" but I was wondering, can't you simply model each timestep which requires different timelength in a separate class and arrange its scheduling according to the specific class interval using real time? – Cratylus Sep 09 '11 at 14:46
  • @user384706 Being inquisitive != being a pain, I'm happy to expand! Whilst that might be appropriate for some problems, it isn't suitable for me because I have to work alongside a system that already works on this 'step' based representation of time. This existing system is used to simulate [multi-agent](http://en.wikipedia.org/wiki/Agent-based_model) systems, where predicting the duration each step takes could be very difficult. I hope that's useful to you somehow :) – obfuscation Sep 09 '11 at 15:00

7 Answers7

2

Well, Quartz is quite powerfull tools, however it has limited configuration possibilities, so if you need specific features, you should propably write your own solution.

However, it's a good idea to study the Quartz source code and data structures, because they have successfully dealt with much problems you would find f.g. inter-process synchronization on database level, running delayed tasks etc.

I've written once my own scheduler, which was adapted to tasks where propably Quartz would not be easy to adapt, but once I've learned Quartz I've understood how much I could improve in my solutions, knowing how it was done in Quartz.

Danubian Sailor
  • 1
  • 38
  • 145
  • 223
  • Thanks for the advice :). To be honest, after asking on SO, checking out the internals of open-source schedulers was my next port of call. The only reason I thought it might be best to ask here first was because of the "non-real time domain" part- I think it's a much less common problem than people wanting "to schedule every 5 minutes", and so may have alternative approaches I should consider! – obfuscation Sep 09 '11 at 14:38
1

A circular linked list might be the data structure you're looking for. Instead of decrementing fields in each task element, you simply increment the index of the 'current' field in the circular list of tasks. A pseudocode structure might look something like this:

tick():
    current = current.next()
    for task : current.tasklist():
        task.execute()

any time you schedule a new task, you just add it in the position N ticks forward of the current 'tick'

mcfinnigan
  • 11,442
  • 35
  • 28
  • Thanks for the suggestion, however would this be suitable in a situation where: 1) Not all timesteps will have a task associated, and 2) The largest scheduling interval may vary during execution? I understand I didn't expalin these assumptions previously! – obfuscation Sep 09 '11 at 14:30
  • So long as the timesteps are always integer multiples of the base value, you should be ok. For taskless steps you simply have an empty entry in the list, and for changing execution time you could add / remove entries in the list. It's not elegant or efficient space wise but it could step neatly around the problem of maintaining counters. – mcfinnigan Sep 09 '11 at 14:34
1

How about this, it uses your own Ticks with executeNextInterval() :

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Scheduler {
    private LinkedList<Interval> intervals = new LinkedList<Scheduler.Interval>();

    public void addTask(Runnable task, int position) {
        if(position<0){
            throw new IllegalArgumentException();
        }
        while(intervals.size() <= position){
            intervals.add(new Interval());
        }
        Interval interval = intervals.get(position);
        interval.add(task);
    }

    public void executeNextInterval(){
        Interval current = intervals.removeFirst();
        current.run();
    }
    private static class Interval {
        private List<Runnable> tasks = new ArrayList<Runnable>();
        public void add(Runnable task) {
            tasks.add(task);
        }
        public void run() {
            for (Runnable task : tasks) {
                task.run();
            }
        }
    }
}

You might want to add some error handling, but it should do your job.

And here are some UnitTests for it :)

import junit.framework.Assert;

import org.junit.Test;

public class TestScheduler {
    private static class Task implements Runnable {
        public boolean didRun = false;
        public void run() {
            didRun = true;
        }       
    }
    Runnable fail = new Runnable() {
        @Override
        public void run() {
            Assert.fail();
        }
    };

    @Test
    public void queue() {
        Scheduler scheduler = new Scheduler();
        Task task = new Task();
        scheduler.addTask(task, 0);
        scheduler.addTask(fail, 1);
        Assert.assertFalse(task.didRun);
        scheduler.executeNextInterval();
        Assert.assertTrue(task.didRun);
    }
    @Test
    public void queueWithGaps() {
        Scheduler scheduler = new Scheduler();
        scheduler.addTask(fail, 1);
        scheduler.executeNextInterval();
    }
    @Test
    public void queueLonger() {
        Scheduler scheduler = new Scheduler();
        Task task0 = new Task();
        scheduler.addTask(task0, 1);
        Task task1 = new Task();
        scheduler.addTask(task1, 1);
        scheduler.addTask(fail, 2);
        scheduler.executeNextInterval();
        scheduler.executeNextInterval();
        Assert.assertTrue(task0.didRun);
        Assert.assertTrue(task1.didRun);
    }
}
flob
  • 3,760
  • 2
  • 34
  • 57
  • Yikes! Why reinvent the wheel? See [ScheduledExecutorService](http://download.oracle.com/javase/1,5.0/docs/api/java/util/concurrent/ScheduledExecutorService.html) (and the example there) – Bohemian Sep 09 '11 at 14:33
  • 1
    But that one is based on time and timed delays, not user ticks. It wont help if you don't know how long each tick will be and if every tick can be of varying length. – flob Sep 09 '11 at 14:41
  • As @flob correctely stated, I am not working in a real time domain. Each tick/step of the system may vary widely in real time duration, and so cannot be represented that way I don't believe. If I was working with a real time domain type problem, I would always stick to standard APIs rather than 'reinventing the wheel' as you said. – obfuscation Sep 09 '11 at 14:44
  • 1
    Thanks, appreciate the accompanying tests as well! Although I may need to optimise in some ways later, developing something following the same idea as this should hopefully fit my purpose. – obfuscation Sep 09 '11 at 15:19
1

Here are a couple of thoughts:

Keep everything simple. If you don't have millions of tasks, there is no need for an optimized data structure (except pride or the urge for premature optimization).

Avoid relative times. Use an absolute internal tick. If you add a task, set the "run next time" to the current tick value. Add it to the list, sort the list by time.

When looking for tasks, start at the head of the list and pick everything which has a time <= current tick, run the task.

Collect all those tasks in another list. After all have run, calculate the "run next time" based on the current tick and the increment (so you don't get tasks that loop), add all of them to the list, sort.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
1

Take a look at the way DelayQueue uses a PriorityQueue to maintain such an ordered list of events. DelayQueue works using real time and hence can use the variable timed wait methods available in Condition and LockSupport. You could implement something like a SyntheticDelayQueue that behaves in the same way as DelayQueue but uses your own synthetic time service. You would obviously have to replace the timed wait/signalling mechanisms that come for free with the jdk though and this might be non trivial to do efficiently.

Matt
  • 8,367
  • 4
  • 31
  • 61
1

If I had to do it, I'd create a simple queue ( linked list variant). This queue would contain a dynamic data structure (simple list for example) containing all the tasks that need to be done. At each time interval (or time-step), the process reads the first node of the queue, executes the instructions it finds in the list of that node. At the end of each execution it would compute the rescheduling and add the new execution to another node in the queue or create nodes up to that position before storing the instruction within that node. The first node is then removed and the second node (now the first) is executed at the next time-step. This system would also not require any integers to be kept track of and all data structures needed are found in the java language. This should solve your problem.

Steinin
  • 541
  • 7
  • 20
  • This sounds pretty much like I was intending to implement myself. My only concern with this approach is the efficiency, however perhaps this is the dreaded 'premature' optimisation problem. – obfuscation Sep 09 '11 at 15:03
  • Build your program first, and if performance is a bother, optimize afterwards. Don't optimize something you haven't made yet. This solution should be efficient enough for your needs. – Steinin Sep 09 '11 at 15:06
0

Use a ScheduledExecutorService. It has everything you need built right in. Here's how simple it is to use:

// Create a single-threaded ScheduledExecutorService
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); // 1 thread

// Schedule something to run in 10 seconds time 
scheduler.schedule(new Runnable() {
    public void run() {
        // Do something
    }}, 10, TimeUnit.SECONDS);

// Schedule something to run in 2 hours time 
scheduler.schedule(new Runnable() {
    public void run() {
        // Do something else
    }}, 2, TimeUnit.HOURS);

// etc as you need
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 1
    Thanks for your answer. However, quoting the OP- "These intervals are in terms of some internal time, that is represented as an integer that is incremented as the program executes (so not equal to real time)". I believe this means that the ScheduledExecutorService would not be useful to me, am I correct? – obfuscation Sep 09 '11 at 14:35
  • 1
    You are *incorrect*: Ultimately, the "internal representation" has to be converted to a real time, otherwise the tasks will never be scheduled to *Actually* run (at a *real* time). This answer can use arbitrary time intervals - that was what I was demonstrating by choosing seconds or hours for the interval. It's sad that the "accepted" answer is so, so crap: It doesn't schedule anything, it merely executes them all in succession without delay. You should *never* re-invent the wheel when a perfectly good wheel exists in the JDK! – Bohemian Sep 09 '11 at 20:21
  • 1
    Perhaps using the term "scheduling" was a mistake, but in fact the accepted answer achieves exactly what I need. So long as the calls to get tasks for the next "interval" are regulated properly (a.k.a. only called once per "internal" time tick) it works, and this is perfectly feasible in my situation. In your solution, what value of the TimeUnit would you suggest I use for "in 3 simulation time steps"? When these are inconsistent in duration I simply do not see it as a feasible answer? – obfuscation Sep 12 '11 at 12:45