可变延迟的ScheduledExecutorService

27

假设我有一个任务,需要从java.util.concurrent.BlockingQueue中获取元素并对其进行处理。

public void scheduleTask(int delay, TimeUnit timeUnit)
{
    scheduledExecutorService.scheduleWithFixedDelay(new Task(queue), 0, delay, timeUnit);
}

如何在频次可动态更改的情况下安排/重新安排任务?

  • 思路是将数据更新流以批处理方式传播到GUI
  • 用户应该能够变化更新频率

我不清楚你为什么要使用阻塞队列。如果你的队列为空,我猜你的计划任务将会被阻塞。这是你的意图吗?这可能会混淆任务调度器的时间。 - Omry Yadan
我选择了ArrayBlockingQueue实现,因为它必须是线程安全的,遵循FIFO排序并且有界限。即使任务被阻塞,它也不应该混淆任务调度,对吧? - parkr
你在使用BlockingQueue实现上是正确的(事实上,ScheduledThreadPoolExecutor在内部也使用了它)。但是,为什么要使用计时器来传播GUI的更新呢?为什么不实时进行呢?是否有太多的更新?你是否担心Swing线程会旋转? - Adamski
我想批量更新。因此,事件可能每毫秒到达一次,但GUI将每100毫秒刷新一次。我认为这在视觉上更具吸引力,并且具有更少的CPU开销。我正在使用Eclipse RCP而不是Swing。 - parkr
如何使用ScheduledExecutorService更改重复任务的速率或周期? - Basil Bourque
5个回答

37

使用schedule(Callable<V>, long, TimeUnit)而不是scheduleAtFixedRatescheduleWithFixedDelay。 然后确保你的Callable在将来的某个时刻重新安排自己或一个新的Callable实例。例如:

// Create Callable instance to schedule.
Callable<Void> c = new Callable<Void>() {
  public Void call() {
   try { 
     // Do work.
   } finally {
     // Reschedule in new Callable, typically with a delay based on the result
     // of this Callable.  In this example the Callable is stateless so we
     // simply reschedule passing a reference to this.
     service.schedule(this, 5000L, TimeUnit.MILLISECONDS);
   }  
   return null;
  }
}

service.schedule(c);

这种方法避免了需要关闭和重新创建ScheduledExecutorService的需求。


1
你可以(应该?)使用 Runnable 替代 Callable<Void> - Thirler
我找不到ScheduledExecutorService的schedule(Callable)函数,只有带有所有参数的函数。你能告诉我在哪里可以找到吗?或者至少修复一下示例,包括0秒的延迟。 - jlanza
1
http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html#schedule-java.util.concurrent.Callable-long-java.util.concurrent.TimeUnit- - Adamski

8

我认为你无法更改固定速率延迟。我认为你需要使用schedule()执行一次性操作,并在完成后再次安排(如果需要,可以修改超时时间)。


1
谢谢 - 我将“delay”变成了实例变量,并添加了一个私有方法来执行以下操作:while(!executorService.isShutdown) { executorService.schedule(new Task(queue), delay, TimeUnit.MILLISECONDS); } - parkr

6

最近我需要使用ScheduledFuture来完成这个任务,但不想包装Runnable等。以下是我的做法:

private ScheduledExecutorService scheduleExecutor;
private ScheduledFuture<?> scheduleManager;
private Runnable timeTask;

public void changeScheduleTime(int timeSeconds){
    //change to hourly update
    if (scheduleManager!= null)
    {
        scheduleManager.cancel(true);
    }
    scheduleManager = scheduleExecutor.scheduleAtFixedRate(timeTask, timeSeconds, timeSeconds, TimeUnit.SECONDS);
}

public void someInitMethod() {

    scheduleExecutor = Executors.newScheduledThreadPool(1);    
    timeTask = new Runnable() {
        public void run() {
            //task code here
            //then check if we need to update task time
            if(checkBoxHour.isChecked()){
                changeScheduleTime(3600);
            }
        }
    };

    //instantiate with default time
    scheduleManager = scheduleExecutor.scheduleAtFixedRate(timeTask, 60, 60, TimeUnit.SECONDS);
}

2
如果您想按照特定间隔处理多个队列任务,那么您应该使用scheduleAtFixedRate,而不是scheduleWithFixedDelay,后者只会等待指定的延迟时间,然后执行队列中的一个任务。
无论哪种情况,在ScheduledExecutorService中的schedule*方法都将返回一个ScheduledFuture引用。如果您想更改速率,可以取消ScheduledFuture并以不同的速率重新安排任务。

scheduleWithFixedDelay(...) - 创建并执行一个周期性的操作,该操作在给定的初始延迟后首次启用,并在一个执行终止和下一个执行开始之间具有给定的延迟。如果任务的任何执行遇到异常,则将抑制后续执行。否则,任务只能通过执行器的取消或终止来终止。 - parkr
1
你能给一个取消和重新安排的代码示例吗?还有正在进行中的任何更新吗? - parkr

0

scheduleWithFixedDelay(...) 返回一个 RunnableScheduledFuture。为了重新安排它,您可以取消并重新安排它。要重新安排它,您只需使用新的 Runnable 包装 RunnableScheduledFuture:

new Runnable() {
    public void run() {
        ((RunnableScheduledFuture)future).run();
    }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接