繁体   English   中英

有没有办法在事件发生或经过特定时间之前安排任务?

[英]Is there a way to schedule a task until an event occurs or a specific amount of time passes?

假设我有一个任务将检查是否满足某个条件。 如果满足这个条件,我应该提前终止任务。 但是,我应该继续运行该任务 N 分钟,同时不断检查是否满足该条件。 在 N 分钟结束时,任务应自行终止。

我一直在研究 Timer、TimerTask 和 ExecutorService,但它们似乎都没有提供我正在寻找的解决方案类型。 例如,这些方法将允许您安排任务运行一次或重复运行,但不能运行特定的时间量(也就是 N 分钟)。

ScheduledExecutorService可以做到这一点。

你需要做什么:

  • 以固定的时间间隔(以时间为单位,例如秒、分钟、小时等)安排您想要执行的工作块
  • 完成工作后,执行ScheduledExecutorService#shutdownNow
  • 要在一段时间后超时,请使用ScheduledExecutorService#awaitTermination延迟计划任务的终止,直到您的阈值过去

任务自行重新安排

Makato 的答案关于建议使用executor service是正确的。 但是结束预定的执行器服务作为结束重复任务的方式并不是最佳的。 通常我们有一个执行器服务处理许多任务。 因此,当我们希望我们的重复任务结束时,执行器服务可能需要继续调度其他任务来运行。

相反,具有讽刺意味的是,不要将重复任务安排为重复 安排任务运行一次 让任务检查其状态。

  • 如果状态要求任务再次运行,则将任务安排在 executor 服务上以进行另一次运行。
  • 如果状态为完成,并且不需要进一步执行该任务,只需选择不向执行器服务提交另一次提交。

完成这项工作的关键是将调度执行器服务的引用传递给任务的构造函数 该任务将该引用保留为其自己的 class 的成员字段。 因此,任务可以使用该引用将自己传递给计划的执行器服务以进行另一次提交。 任务变成自调度。

这是此类任务示例的源代码。

请注意,除了传递对计划执行器服务的引用之外,我们还传递了一个Instant ,表示我们想要停止重复任务的目标时刻。 我们传递了一个Duration object 来表示此任务执行之间的等待时间。

检查该run方法。 该任务将当前时刻与目标时刻进行比较。 如果尚未通过,则任务安排它自己的下一次执行。 如果通过,无需重新安排,任务完成。

package work.basil.example.concurrency;

import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ExpiringTask implements Runnable
{
    private final Instant timeLimit;
    private final Duration untilNextRun;
    private final ScheduledExecutorService scheduledExecutorService;

    public ExpiringTask ( final Instant timeLimit , final Duration untilNextRun , final ScheduledExecutorService scheduledExecutorService )
    {
        this.timeLimit = Objects.requireNonNull( timeLimit );
        this.untilNextRun = Objects.requireNonNull( untilNextRun );
        this.scheduledExecutorService = Objects.requireNonNull( scheduledExecutorService );
    }

    @Override
    public void run ( )
    {
        System.out.println( "INFO - Running the `run` method of our ExpiringTask. Now: " + Instant.now() );
        if ( Instant.now().isAfter( this.timeLimit ) )
        {
            System.out.println( "☑️ Mission accomplished. Our task has expired. Now: " + Instant.now() );
        }
        else
        {
            System.out.println( "⏲️ Not yet expired. Reschedule to check again. Now: " + Instant.now() );
            this.scheduledExecutorService.schedule( this , this.untilNextRun.toNanos() , TimeUnit.NANOSECONDS );
        }
    }
}

在实际工作中,在重新安排任务时,我会检查执行器服务是否仍然可用。 为此,请调用ExecutorSerivce#isShutdown 为简洁起见,上述代码中省略了此步骤。

如何运行让该任务运行? 这是应用程序的主要部分,用于演示此任务在重新安排自身时重复运行。

首先,我们将目标时间限制设定为从现在起五秒。 然后我们指定每次运行之间等待一秒钟。 我们得到一个预定的执行器服务。 请务必保留该引用,因为您必须小心最终优雅地关闭执行器服务 - 否则它的支持线程池可能会无限期地继续运行,就像僵尸♂️。

下一步是实例化我们的任务,将片段传递给它的构造函数。 最后,为了让事情正常进行,我们告诉预定的执行器服务在一定的延迟后运行我们的任务。 对于第一次运行,我们希望立即运行,因此我们指定延迟为零纳秒。

Instant timeLimit = Instant.now().plus( Duration.ofSeconds( 5 ) );
Duration untilNextRun = Duration.ofSeconds( 1 );
ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
ExpiringTask task = new ExpiringTask( timeLimit , untilNextRun , ses );
ses.schedule( task , 0 , TimeUnit.NANOSECONDS );

这涵盖了主要思想。 这是完整的应用程序 class 包含上面直接摘录的代码。

package work.basil.example.concurrency;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class KeepRunningUntil
{
    public static void main ( String[] args )
    {
        KeepRunningUntil app = new KeepRunningUntil();
        app.demo();
        System.out.println( "INFO - Demo ending. Now: " + Instant.now() );
    }

    private void demo ( )
    {
        System.out.println( "INFO - 'demo' method starting. " + Instant.now() );

        Instant timeLimit = Instant.now().plus( Duration.ofSeconds( 5 ) );
        Duration untilNextRun = Duration.ofSeconds( 1 );
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        ExpiringTask task = new ExpiringTask( timeLimit , untilNextRun , ses );
        ses.schedule( task , 0 , TimeUnit.NANOSECONDS );

        // Wait a while for our background task to do its thing.
        try { Thread.sleep( Duration.ofSeconds( 10 ).toMillis() ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
        this.shutdownAndAwaitTermination( ses );

        System.out.println( "INFO - 'demo' method ending. " + Instant.now() );
    }

    private void shutdownAndAwaitTermination ( ExecutorService executorService )
    {
        // Boilerplate taken from Javadoc.
        // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ExecutorService.html#isShutdown()

        executorService.shutdown(); // Disable new tasks from being submitted
        try
        {
            // Wait a while for existing tasks to terminate
            if ( ! executorService.awaitTermination( 60 , TimeUnit.SECONDS ) )
            {
                executorService.shutdownNow(); // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if ( ! executorService.awaitTermination( 60 , TimeUnit.SECONDS ) )
                { System.err.println( "Pool did not terminate" ); }
            }
        }
        catch ( InterruptedException ex )
        {
            // (Re-)Cancel if current thread also interrupted
            executorService.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }
    }
}

跑的时候。

INFO - 'demo' method starting. 2022-08-25T08:16:28.384638Z
INFO - Running the `run` method of our ExpiringTask. Now: 2022-08-25T08:16:28.402471Z
⏲️ Not yet expired. Reschedule to check again. Now: 2022-08-25T08:16:28.402657Z
INFO - Running the `run` method of our ExpiringTask. Now: 2022-08-25T08:16:29.408010Z
⏲️ Not yet expired. Reschedule to check again. Now: 2022-08-25T08:16:29.408126Z
INFO - Running the `run` method of our ExpiringTask. Now: 2022-08-25T08:16:30.413336Z
⏲️ Not yet expired. Reschedule to check again. Now: 2022-08-25T08:16:30.413599Z
INFO - Running the `run` method of our ExpiringTask. Now: 2022-08-25T08:16:31.416144Z
⏲️ Not yet expired. Reschedule to check again. Now: 2022-08-25T08:16:31.416230Z
INFO - Running the `run` method of our ExpiringTask. Now: 2022-08-25T08:16:32.421484Z
⏲️ Not yet expired. Reschedule to check again. Now: 2022-08-25T08:16:32.421873Z
INFO - Running the `run` method of our ExpiringTask. Now: 2022-08-25T08:16:33.425519Z
☑️ Mission accomplished. Our task has expired. Now: 2022-08-25T08:16:33.425863Z
INFO - 'demo' method ending. 2022-08-25T08:16:38.407566Z
INFO - Demo ending. Now: 2022-08-25T08:16:38.407755Z

这里提到的其他答案都很好。 只是给出另一个可能更简单的想法。

  1. 安排您的任务定期运行,不用担心终止
  2. 创建一个 class 让我们称它为TaskManager并使其成员之一成为ExecutorService 将用于任务调度的ExecutorService传递给TaskManager 此外,在您的任务管理器中添加一个计数器,您的任务将在每次运行时递增。 显然,您的TaskManager应该有一个您的任务将使用的 static 方法incrementCounter() 此外,您的TaskManager应该有一个方法 static 方法teminateTask()
  3. 因此,现在当计数器达到您的限制时, TaskManager将调用您的ExecutorService的方法shutDown() 或者,如果您的内部逻辑中的任务得出结论,它的终止时间将调用TaskManager的方法teminateTask()

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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