从Handler迁移到ScheduledExecutorService以进行调度

时间:2014-08-08 21:54:39

标签: java android multithreading concurrency scheduler

我的目标是安排以非均匀费率发生的经常性工作。我将从第一个片段迁移到第二个片段:

第一

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

第二

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);
}

我的问题是:

1-在第一个片段中,mHandler引用的线程在作业之间是否可以自由执行其他任务或处理其他消息?

2-但是在第二个片段中,Thread总是忙于循环。正确?

3-如何重写第二个代码,以免我在作业之间松散线程活动(延迟)?

非常感谢任何帮助

3 个答案:

答案 0 :(得分:1)

您的第二个代码无法按预期工作。在第一个任务被安排并等待执行之后,while循环继续安排更多任务,所有任务都具有相同的延迟。所以你最终会有数千个,可能是数百万个任务。当然,因为主线程在没有任何等待的情况下运行无限循环,所以它一直很忙。这可能不是你想要的。

你最好使用simliar方法而不是上面的处理程序:

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

(当然,您还应检查重新安排时指定的延迟是否为负值。)

更新:如果您需要单独处理每个执行的结果,可能会出现与您的第二个代码示例类似的其他方法。它会调度任务执行,并将结果移交给Consumer,只要它可用。 (注意循环内的future.get()导致循环线程暂停直到任务完成。)

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());
    }
}

还要注意中断检查,以便其他线程可以通过中断循环线程来停止执行。这样可以简化此方法在另一个任务中的使用,以防您想在后台线程上运行它。

Schedule可以是一个功能界面,提供对计划信息的访问:

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

顺便说一下:android.os.Handler是一个非常好的方式来做你想要的android。因此,如果您确实需要其功能(例如,获得ScheduledExecutorService结果),则只应迁移到Future

答案 1 :(得分:0)

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);
    }
}

答案 2 :(得分:0)

我有一些类似的问题..我试图以不同的速度安排不同的工作,我发现使用Quartz Scheduler库来处理我的所有调度问题真正解决:)

对于您的问题:以非均匀费率开展工作,您可以轻松实施TriggerListener并在完成时重新安排nextTime()

上的相同工作

Quartz Scheduler可以轻松地与Spring,Maven集成,并且可以处理各种情况,例如失败的作业或线程异常。

简单示例(来自文档)

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();