2
votes

I need to schedule to a new Date for everytime a task it's executed. I've seen many examples where the period or interval is set by millis and stays for every iteration but i can't find any that takes a date parameter for next execution

I tried the @Scheduled annotation since I am working with Spring, but I do not know if there is a possibility to pass a parameter.

Examples i've seen

Example 1:

@Scheduled(fixedRate = 20000)
    public void scheduler() {
        log.info("scheduler");
        log.info("Current Thread " + Thread.currentThread().getName());
        log.info("Current Thread " + Thread.currentThread().getId());
}

Example 2:

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(myRunnable, 10, 10, TimeUnit.MINUTES);

I expect to read a Date from a db table to schedule my task for new iteration

Ty for the help!

Edit

Note: There will also be a time when I need to decide where to stop the next iteration, so I'm trying to call the schedule task by a method

2
Check the accepted answer here: stackoverflow.com/questions/14630539/…Nima Ajdari

2 Answers

1
votes

You can register a new TimerTask, execute the desired logic, and register a new TimerTask at the completion of the desired logic:

public class Starter {

    public void execute() {
        Timer timer = new Timer();
        Date firstExecutionDate = // ... compute ...
        timer.schedule(
            new RepeatedTimerTask(timer, this::toDoUponEachExecution, this::findNextExecutionDate),
            firstExecutionDate
        );
    }

    private Date findNextExecutionDate() {
        // ... compute ...
    }

    private void toDoUponEachExecution() {
        // ... do something ...
    }
}

public class RepeatedTimerTask extends TimerTask {

    private final Timer timer;
    private final Runnable logic;
    private final Supplier<Date> nextExecution;

    public RepeatedTimerTask(Timer timer, Runnable logic, Supplier<Date> nextExecution) {
        this.timer = timer;
        this.logic = logic;
        this.nextExecution = nextExecution;
    }

    @Override
    public void run() {
        logic.run();
        timer.schedule(this, nextExecution.get());
    }
}
1
votes

I avoid using Spring, so I cannot help you there. But I can guide you through the use of ScheduledExecutorService to accomplish your goal.

ScheduledExecutorService::schedule​( Runnable command, long delay, TimeUnit unit )

You are partially correct about the ScheduledExecutorService: Two of its three scheduling strategies are designed to keep regular intervals between runs:

But the third strategy lets you set the next run with any amount of delay you wish.

If you want a single task to be executed repeatedly but not concurrently, use a single-thread executor.

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor() ;

On that ScheduledExecutorService, schedule your task. And make the last step of that task be the chore of scheduling the next occurrence. We have a perpetual motion machine, each time the task runs, it schedules the next run, indefinitely.

Define your Runnable task.

    Runnable runnable = new Runnable() {
        @Override
        public void run ( ) {
            // Do the work of this task.
            ZonedDateTime zdt = ZonedDateTime.now( ZoneId.systemDefault() ); // Capture the current moment.
            System.out.println( "Current moment: " + zdt ); // Report the current moment.
            // Schedule the next run of this task.
            scheduledExecutorService.schedule( this , 10L , TimeUnit.SECONDS );  // Delay will not be *exactly* this amount of time due to interruptions of scheduling cores on CPU and threads by the JVM and host OS.
        }
    };

Then run it.

    // Jump-start this perpetual motion machine.
    scheduledExecutorService.schedule( runnable , 0L , TimeUnit.SECONDS );  // Start immediately, no delay.

Let the executor do its work repeatedly for a certain length of time. Sleep the main thread while the executor service runs on a background thread(s).

    try {
        Thread.sleep( TimeUnit.MINUTES.toMillis( 2 ) );  // Let our app, and the executor, run for 2 minutes, then shut them both down.
    } catch ( InterruptedException e ) {
        e.printStackTrace();
    }

Remember to always shutdown the executor. Otherwise its background thread(s) may continue running long after your main app has exited.

    scheduledExecutorService.shutdown();
    System.out.println( "INFO - Executor shutting down. App exiting. " + ZonedDateTime.now( ZoneId.systemDefault() ) );

Tip: Always wrap your Runnable code in a try-catch for all exceptions. Any uncaught exception reaching the executor service will cause the executor to immediately halt, and halt silently.

    Runnable runnable = new Runnable() {
        @Override
        public void run ( ) {
            try {
                // Do the work of this task.
                ZonedDateTime zdt = ZonedDateTime.now( ZoneId.systemDefault() ); // Capture the current moment.
                System.out.println( "Current moment: " + zdt ); // Report the current moment.
                // Schedule the next run of this task.
                scheduledExecutorService.schedule( this , 10L , TimeUnit.SECONDS );  // Delay will not be *exactly* this amount of time due to interruptions of scheduling cores on CPU and threads by the JVM and host OS.
            } catch ( Exception e ) {
                // TODO: Handle unexpected exeption.
                System.out.println( "ERROR - unexpected exception caught on its way to reaching a scheduled executor service. Message # 55cbae82-8492-4638-9630-60c5b28ad876." );
            }
        }
    };

I expect to read a Date from a db table to schedule my task for new iteration

Never use Date or Calendar. Those terrible classes were supplanted years ago by the java.time with the adoption of JSR 310.

As of JDBC 4.2 and later, we can directly exchange java.time objects with the database.

OffsetDateTime now = OffsetDateTime.now( ZoneOffset.UTC ) ;
OffsetDateTime later = myResultSet.getObject( … , OffsetDateTime.class ) ;
if( ! now.isBefore( later ) ) { … } // Verify the future moment is indeed in the future.

Calculate elapsed time, the amount of time we want to delay until our next scheduled run.

Duration d = Duration.between( now , odt ) ;
long seconds = d.toSeconds() ; // Truncates any fractional second.

Use that number of seconds to schedule the next run.

scheduledExecutorService.schedule( this , seconds , TimeUnit.SECONDS ); 

So the Runnable now looks like this.

    Runnable runnable = new Runnable() {
        @Override
        public void run ( ) {
            try {
                // Do the work of this task.
                ZonedDateTime zdt = ZonedDateTime.now( ZoneId.systemDefault() ); // Capture the current moment.
                System.out.println( "Current moment: " + zdt ); // Report the current moment.
                // Schedule the next run of this task.
                OffsetDateTime now = OffsetDateTime.now( ZoneOffset.UTC ) ;
                … do your database query …
                OffsetDateTime later = myResultSet.getObject( … , OffsetDateTime.class ) ;
                if( ! now.isBefore( later ) ) { … } // Verify the future moment is indeed in the future.
                Duration d = Duration.between( now , odt ) ;
                long seconds = d.toSeconds() ; // Truncates any fractional second.
                scheduledExecutorService.schedule( this , seconds , TimeUnit.SECONDS );  // Delay will not be *exactly* this amount of time due to interruptions of scheduling cores on CPU and threads by the JVM and host OS.
            } catch ( Exception e ) {
                // TODO: Handle unexpected exeption.
                System.out.println( "ERROR - unexpected exception caught on its way to reaching a scheduled executor service. Message # 55cbae82-8492-4638-9630-60c5b28ad876." );
            }
        }
    };

Here is the complete example in a single .java file but without the database query.

package work.basil.example;

import java.util.concurrent.*;
import java.time.*;

public class ScheduleNextTaskExample {
    public static void main ( String[] args ) {
        ScheduleNextTaskExample app = new ScheduleNextTaskExample();
        app.doIt();
    }

    private void doIt ( ) {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

        Runnable runnable = new Runnable() {
            @Override
            public void run ( ) {
                try {
                    ZonedDateTime zdt = ZonedDateTime.now( ZoneId.systemDefault() ); // Capture the current moment.
                    System.out.println( "Current moment: " + zdt ); // Report the current moment.
                    scheduledExecutorService.schedule( this , 10L , TimeUnit.SECONDS );  // Delay will not be *exactly* this amount of time due to interruptions of scheduling cores on CPU and threads by the JVM and host OS.
                } catch ( Exception e ) {
                    // TODO: Handle unexpected exeption.
                    System.out.println( "ERROR - unexpected exception caught on its way to reaching a scheduled executor service. Message # 55cbae82-8492-4638-9630-60c5b28ad876." );
                }
            }
        };

        // Jump-start this perpetual motion machine.
        scheduledExecutorService.schedule( runnable , 0L , TimeUnit.SECONDS );  // Start immediately, no delay.
        try {
            Thread.sleep( TimeUnit.MINUTES.toMillis( 2 ) );  // Let our app, and the executor, run for 2 minutes, then shut them both down.
        } catch ( InterruptedException e ) {
            e.printStackTrace();
        }
        scheduledExecutorService.shutdown();
        System.out.println( "INFO - Executor shutting down. App exiting. " + ZonedDateTime.now( ZoneId.systemDefault() ) );

    }
}