简体   繁体   中英

migrating from Handler to ScheduledExecutorService for scheduling

My goal is to schedule a recurrent job that happens on a non-even rate. I am going to migrate from first snippet to the second:

1st:

Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        if (msg.what == MSG1) {               
            //recurrent job here              
            long nextTime = nextTime();
            sendMessageAtTime(obtainMessage(MSG1), nextTime);
            }
        }
    }
};

2nd:

ScheduledExecutorService mExecutor;
while (true){
        mExecutor.schedule(new Callable() {
                public Object call() throws Exception {
                    long startTime = SystemClock.uptimeMillis();                       
                    //recurrent job here 
                    delay = nextTime() - startTime ;
                    return true;
                }
            }, delay, TimeUnit.MILLISECONDS);
}

My questions are:

1- is it true in the first snippet that the thread, to which the mHandler is referring, is free between jobs to do other tasks or handle other messages?

2- However in the second snippet, Thread is always busy doing the loop. right?

3- How can I rewrite the second code so that I won't loose thread activity between jobs (in delays)?

Any help is highly appreciated

Your second code won't work as expected. After the first task has been scheduled and is waiting to be executed, the while loop continues to schedule more tasks, all of them with the same delay. So you'll end up having thousands, probably millions of tasks. And of course, because the main thread is running an infinite loop without any wait, it is busy all the time. This is probably not what you want.

You should better use a simliar approach than the handler uses above:

final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.schedule(() -> {
    // do work
    // reschedule
    executor.schedule(this, nextTime() - System.currentTimeMillis());
}, delay, TimeUnit.MILLISECONDS);

(Of course you should also check that the delay you specify when rescheduling is not negative).

Update: If you need to process the result of each execution individually, another approach similar to your second code example is possibly what you want. It schedules the task executions insisde a loop and hands over the result to a Consumer , as soon as it is available. (Note the future.get() inside the loop which causes the looping thread to pause until the task is done).

public static <T> void schedule(ScheduledExecutorService scheduler,
            Schedule schedule, Callable<T> task, Consumer<? super T> consumer)
            throws InterruptedException, ExecutionException {
    while (true) {
        if (Thread.interrupted()) throw new InterruptedException();

        long delay = schedule.nextTime() - System.currentTimeMillis();
        if (delay < 0) continue; // skip this step

        ScheduledFuture<? extends T> future = scheduler.schedule(task,
                                              delay, schedule.getUnit());
        consumer.accept(future.get());
    }
}

Also note the interruption check, so that other threads can stop execution by interrupting the looping thread. This simplifies the usage of this method inside another task in case you want to run it on a background thread too.

Schedule could be a functional interface that provides access to the scheduling information:

@FunctionalInterface
public interface Schedule {
    long nextTime();
    default TimeUnit getUnit() { return TimeUnit.MILLISECONDS; }
}

Btw.: The android.os.Handler is a very nice way to do what you want in android. So you should only migrate to ScheduledExecutorService if you really need its features (eg getting a Future result).

public class RecurrentJobThatHappensOnANonEvenRate {

    /**
     * Consider you have your job defined as below
     */
    abstract class TheJob implements Runnable {

        @Override
        public void run() {
            long startTime = System.currentTimeMillis();

            doRecurrentJob();

            schedule(nextTime() - startTime);
        }

        void doRecurrentJob() {
            // Do the job
        }

        long nextTime() {
            // calculate next execution time
            long randomDelay = Math.round(5000 + Math.random() * 5000);
            return System.currentTimeMillis() + randomDelay;
        }

        public abstract void schedule(long delay);
    };

    /**
     * Example using `ScheduledExecutorService`.
     */
    public void exampleWithScheduledExecutorService() {
        TheJob theJob = new TheJob() {

            private final ScheduledExecutorService executor =
                    Executors.newScheduledThreadPool(1);

            @Override
            public void schedule(long delay) {
                executor.schedule(this, delay, TimeUnit.MILLISECONDS);
            }
        };
        theJob.schedule(1500);
    }

    /**
     * Example with `Handler` and using already existing `Thread` with
     * `Looper` (most probably the main looper).
     */
    public void exampleWithHandlerAndMainLooper() {

        TheJob theJob = new TheJob() {

            private final Handler handler =
                    // new Handler();
                    // or if you are not in the main thread:
                    new Handler(Looper.getMainLooper());

            @Override
            public void schedule(long delay) {
                handler.postDelayed(this, delay);
            }
        };
        theJob.schedule(1500);
    }

    /**
     * Example with `Handler` and `HandlerThread` (a convenience thread
     * class with looper).
     */
    public void exampleWithHandlerAndHandlerThreadsLooper() {

        TheJob theJob = new TheJob() {

            private final HandlerThread handlerThread;
            private final Handler handler;
            private final long killThreadAt;
            {
                handlerThread = new HandlerThread("myThread");
                // handler thread must be quit when you no longer use it.
                // see nextTime() method below.
                killThreadAt = System.currentTimeMillis() + 30000;
                // alternatively you can set it to be a daemon thread.
                // handlerThread.setDaemon(true);
                handlerThread.start();
                handler = new Handler(handlerThread.getLooper());
            }

            @Override
            public void schedule(long delay) {
                handler.postDelayed(this, delay);
            }

            @Override
            long nextTime() {
                long nextTime = super.nextTime();
                if(nextTime() > killThreadAt) {
                    handlerThread.quit();
                }
                return nextTime;
            }
        };
        theJob.schedule(1500);
    }
}

I had some similar issues .. I was trying to schedule different jobs at different rates and I found using the Quartz Scheduler library to handle all my scheduling problems a real relieve :)

For your problem: firing a job at a non-even rate, you could easily implement a TriggerListener and on completion reschedule the same job at nextTime()

The Quartz Scheduler easily integrates with Spring, Maven and has handles for all kind of scenarios like misfired jobs or thread exceptions.

Simple example (from the docs)

SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();

// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
    .withIdentity("job1", "group1")
    .build();

// compute a time that is on the next round minute
int minutesInterval = nextTime();

// Trigger the job to run on the next round minute and repeat it forever
Trigger trigger = newTrigger()
    .withIdentity("trigger1", "group1")
    .withSchedule(
        simpleSchedule()
        .withIntervalInMinutes(minutesInterval)
        .repeatForever()
     )
    .build();

// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
sched.start();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM