简体   繁体   中英

ScheduledExecutorService cancel task inside my task

I have a timed task that will make an http call and then check the return result. If the result is a certain value, I will do some stuff, and then this timed task can be terminated and no longer needs to be scheduled. How can I cancel this schedule within my task? ? Does ScheduledExecutorService provide a parameter similar to the termination condition?

        ScheduledExecutorService service = new ScheduledThreadPoolExecutor(5);

        service.scheduleAtFixedRate(() -> {
            // http request
            // check
            // result is xxx, no more schedule
            // How can I stop this task schedule within my task? ?
            
        }, 1, 60, TimeUnit.SECONDS);

Make a non-scheduled executor service.

Make a scheduled executor service.

Instantiate your repeating task as a Runnable or Callable . Pass to its constructor a reference to both executor services.

Schedule your task on the scheduled executor service.

Every time the task runs, check for your quit condition.

  • When that condition is false, do nothing more. Let the run / call method complete.
  • When that condition is true, submit a new task onto the non-scheduled executor service. That new task took a reference to the scheduled executor service as an argument to its constructor. The run / call method of that task cancels the passed scheduled executor service.

To do the canceling, the task calls ScheduledExecutorService#shutdown and #awaitTermination .

The simplest thing that can possibly work is to throw an exception from the body of the scheduled function when you want to stop. I think it's ugly.See the javadoc here .

Another way is not to schedule periodically (ie not use scheduleAtFixedRate), but schedule just once and reschedule (or not) after the result of the call is checked.

You will have to keep track of how much time the call took if you want to approximate what scheduleAtFixedRate does.

Note also that using scheduleAtFixedRate, if your http call takes more than the interval you specified and your ScheduledExecutorService has some parallelism (you gave it 5 threads) you will have multiple concurrent execution of the http call. This might or might not be what you want.

This solution approximates scheduleAtFixedRate and degrades to scheduleAtFixedDelay when your call takes more than 60 seconds:

private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

public static void main(String[] args) {
    scheduler.schedule(() -> {
        performHttpCallAndScheduleAgainIfNeeded(scheduler);
    }, 1, TimeUnit.SECONDS);
}

private static void performHttpCallAndScheduleAgainIfNeeded(ScheduledExecutorService scheduler) {
    final long startTime = System.currentTimeMillis();
    boolean callWasOk = performHttpCall();
    if (!callWasOk) {
        final long elapsed = System.currentTimeMillis() - startTime;
        final long millisToWaitForNextAttempt = Math.max(0, 60000 - elapsed);
        scheduler.schedule(() -> performHttpCallAndScheduleAgainIfNeeded(scheduler), millisToWaitForNextAttempt, TimeUnit.MILLISECONDS);
    } else {
        // the call was ok... you have nothing to do.
    }
}

private static boolean performHttpCall() {
    // do whatever you need to do here...
}

If you instead still want to use scheduleAtFixedRate or scheduleWithFixedDelay, you have to keep some state around (beware of concurrency) and note that both methods return a future that you have to use to cancel them.

The trick here is that you will have to pass the result of your scheduleAtFixRate call to the actual scheduled function. You can solve this chicked-egg problem by using a container instead of the value: here I used CompletableFuture as a container:

public static void main(String[] args) {
    final CompletableFuture<ScheduledFuture<?>> cancellablePeriodicTask = new CompletableFuture<>();
    final ScheduledFuture<?> cancellable = scheduler.scheduleAtFixedRate(() -> {
        performHttpCallAndScheduleAgainIfNeeded(cancellablePeriodicTask);
    }, 1, 60, TimeUnit.SECONDS);
    cancellablePeriodicTask.complete(cancellable);
}

private static void performHttpCallAndScheduleAgainIfNeeded(CompletableFuture<ScheduledFuture<?>> cancellable) {
    boolean callWasOk = performHttpCall();
    if (callWasOk) {
        // here you cancel
        cancellable.whenComplete((scheduledFuture, throwable) -> {
          if (throwable == null) {  
              scheduledFuture.cancel(true);
          }
        });
    }

    // call was not ok here, nothing to do since the scheduler will call this again.
}

private static boolean performHttpCall() {
    // do whatever you need to do here...
}

In this approach CompletableFuture as mechanism for passing values between threads is not the only thing that works (SynchronousQueue also works here).

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