Runnableがあります。 scheduleWithFixedDelay でScheduledExecutorServiceを使用して、このRunnableを実行するようにスケジュールするクラスがあります。
このクラスを変更して、Runnableの固定遅延実行をスケジュールしますeither無限に、or渡されるパラメーターに応じて、特定の回数実行されるまでコンストラクターに。
可能であれば、同じRunnableを使用したいと思います。これは、概念的には「実行」すべきものと同じであるためです。
2つのRunnableがあり、1つは多数の実行後にスケジュールをキャンセルする(カウントを保持する)ものとそうでないものがあります。
_public class MyClass{
private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
public enum Mode{
INDEFINITE, FIXED_NO_OF_TIMES
}
public MyClass(Mode mode){
if(mode == Mode.INDEFINITE){
scheduler.scheduleWithFixedDelay(new DoSomethingTask(), 0, 100, TimeUnit.MILLISECONDS);
}else if(mode == Mode.FIXED_NO_OF_TIMES){
scheduler.scheduleWithFixedDelay(new DoSomethingNTimesTask(), 0, 100, TimeUnit.MILLISECONDS);
}
}
private class DoSomethingTask implements Runnable{
@Override
public void run(){
doSomething();
}
}
private class DoSomethingNTimesTask implements Runnable{
private int count = 0;
@Override
public void run(){
doSomething();
count++;
if(count > 42){
// Cancel the scheduling.
// Can you do this inside the run method, presumably using
// the Future returned by the schedule method? Is it a good idea?
}
}
}
private void doSomething(){
// do something
}
}
_
DoSomethingメソッドの実行用にRunnableを1つだけ用意します。スケジューリングをRunnableに結び付けるのは間違っているように感じます。これについてどう思う?
定期的に実行するコードの実行用に単一のRunnableを用意します。最初のRunnableが何回実行されたかを確認し、特定の量に達したらキャンセルする、別個のスケジュールされたRunnableを用意します。これは非同期であるため、正確ではない場合があります。少し面倒です。これについてどう思う?
ScheduledExecutorServiceを拡張し、メソッド「scheduleWithFixedDelayNTimes」を追加します。おそらく、そのようなクラスはすでに存在していますか?現在、Executors.newSingleThreadScheduledExecutor();
を使用して、ScheduledExecutorServiceインスタンスを取得しています。拡張されたScheduledExecutorServiceをインスタンス化するには、おそらく同様の機能を実装する必要があります。これには注意が必要です。これについてどう思う?
スケジューラを使用できませんでした。代わりに次のようなものがあります:
_for(int i = 0; i < numTimesToRun; i++){
doSomething();
Thread.sleep(delay);
}
_
そして、それをいくつかのスレッドで実行します。あなたはそれをどう思いますか?実行可能ファイルを引き続き使用して、runメソッドを直接呼び出すこともできます。
どんな提案も歓迎します。私の目標を達成するための「ベストプラクティス」の方法を見つけるための議論を探しています。
Futureでcancel()メソッドを使用できます。 scheduleAtFixedRate のjavadocから
Otherwise, the task will only terminate via cancellation or termination of the executor
以下は、オリジナルが実行された回数を追跡し、N回実行した後にキャンセルするRunnableを別のRunnableにラップするサンプルコードです。
public void runNTimes(Runnable task, int maxRunCount, long period, TimeUnit unit, ScheduledExecutorService executor) {
new FixedExecutionRunnable(task, maxRunCount).runNTimes(executor, period, unit);
}
class FixedExecutionRunnable implements Runnable {
private final AtomicInteger runCount = new AtomicInteger();
private final Runnable delegate;
private volatile ScheduledFuture<?> self;
private final int maxRunCount;
public FixedExecutionRunnable(Runnable delegate, int maxRunCount) {
this.delegate = delegate;
this.maxRunCount = maxRunCount;
}
@Override
public void run() {
delegate.run();
if(runCount.incrementAndGet() == maxRunCount) {
boolean interrupted = false;
try {
while(self == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
interrupted = true;
}
}
self.cancel(false);
} finally {
if(interrupted) {
Thread.currentThread().interrupt();
}
}
}
}
public void runNTimes(ScheduledExecutorService executor, long period, TimeUnit unit) {
self = executor.scheduleAtFixedRate(this, 0, period, unit);
}
}
APIの説明から引用( ScheduledExecutorService.scheduleWithFixedDelay
):
指定された初期遅延の後に最初に有効になり、その後、1つの実行の終了から次の実行の開始までの間に指定された遅延で有効になる定期的なアクションを作成して実行します。 タスクの実行で例外が発生すると、後続の実行は抑制されます。それ以外の場合、タスクはキャンセルまたはエグゼキューターの終了によってのみ終了します。
そのため、最も簡単なのは"例外をスローするだけ"です(これは悪い習慣と見なされますが):
static class MyTask implements Runnable {
private int runs = 0;
@Override
public void run() {
System.out.println(runs);
if (++runs >= 20)
throw new RuntimeException();
}
}
public static void main(String[] args) {
ScheduledExecutorService s = Executors.newSingleThreadScheduledExecutor();
s.scheduleWithFixedDelay(new MyTask(), 0, 100, TimeUnit.MILLISECONDS);
}
これまでのところ、sbridgesソリューションは、あなたが言及したことを除いて、Runnable
自体に実行回数を処理する責任を残しているという点で、最もクリーンなソリューションのようです。これに関係するべきではなく、代わりに繰り返しはスケジューリングを処理するクラスのパラメーターでなければなりません。これを実現するには、Runnables
の新しいexecutorクラスを導入する次の設計をお勧めします。このクラスは、タスクをスケジュールするための2つのパブリックメソッドを提供します。これらは、標準のRunnables
であり、有限または無限の繰り返しがあります。必要に応じて、同じRunnable
を有限および無限スケジューリングに渡すことができます(Runnable
クラスを拡張して有限の繰り返しを提供するすべての提案されたソリューションでは不可能です)。有限の繰り返しをキャンセルする処理は、スケジューラクラスに完全にカプセル化されています。
class MaxNScheduler
{
public enum ScheduleType
{
FixedRate, FixedDelay
}
private ScheduledExecutorService executorService =
Executors.newSingleThreadScheduledExecutor();
public ScheduledFuture<?> scheduleInfinitely(Runnable task, ScheduleType type,
long initialDelay, long period, TimeUnit unit)
{
return scheduleNTimes(task, -1, type, initialDelay, period, unit);
}
/** schedule with count repetitions */
public ScheduledFuture<?> scheduleNTimes(Runnable task, int repetitions,
ScheduleType type, long initialDelay, long period, TimeUnit unit)
{
RunnableWrapper wrapper = new RunnableWrapper(task, repetitions);
ScheduledFuture<?> future;
if(type == ScheduleType.FixedDelay)
future = executorService.scheduleWithFixedDelay(wrapper,
initialDelay, period, TimeUnit.MILLISECONDS);
else
future = executorService.scheduleAtFixedRate(wrapper,
initialDelay, period, TimeUnit.MILLISECONDS);
synchronized(wrapper)
{
wrapper.self = future;
wrapper.notify(); // notify wrapper that it nows about it's future (pun intended)
}
return future;
}
private static class RunnableWrapper implements Runnable
{
private final Runnable realRunnable;
private int repetitions = -1;
ScheduledFuture<?> self = null;
RunnableWrapper(Runnable realRunnable, int repetitions)
{
this.realRunnable = realRunnable;
this.repetitions = repetitions;
}
private boolean isInfinite() { return repetitions < 0; }
private boolean isFinished() { return repetitions == 0; }
@Override
public void run()
{
if(!isFinished()) // guard for calls to run when it should be cancelled already
{
realRunnable.run();
if(!isInfinite())
{
repetitions--;
if(isFinished())
{
synchronized(this) // need to wait until self is actually set
{
if(self == null)
{
try { wait(); } catch(Exception e) { /* should not happen... */ }
}
self.cancel(false); // cancel gracefully (not throwing InterruptedException)
}
}
}
}
}
}
}
公平を期すために、繰り返しを管理するロジックはaRunnable
を使用していますが、Runnable
の完全に内部的なMaxNScheduler
であり、一方、スケジューリングのために渡されるRunnable
タスクは、スケジューリングの性質に関係する必要はありません。また、この懸念は、必要に応じて、いつでもコールバックを提供することにより、スケジューラに簡単に移動できますRunnableWrapper.run
が実行されました。これによりコードが少し複雑になり、RunnableWrapper
sのマップとそれに対応する繰り返しを保持する必要が生じます。そのため、カウンターをRunnableWrapper
クラスに保持することを選択しました。
また、自己を設定するときにラッパーに同期を追加しました。理論的には、実行が終了した時点でselfがまだ割り当てられていない可能性があるため、これが必要です(非常に理論的なシナリオですが、1回の繰り返しのみが可能です)。
キャンセルはInterruptedException
をスローせずに適切に処理され、キャンセルが実行される前に別のラウンドがスケジュールされている場合、RunnableWrapper
は基になるRunnable
を呼び出しません。
特定のタイムアウトまでのポーリングなどのユースケースでは、Future.get()
を使用してより簡単なソリューションでアプローチできます。
/* Define task */
public class Poll implements Runnable {
@Override
public void run() {
// Polling logic
}
}
/* Create executor service */
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
/* Schedule task - poll every 500ms */
ScheduledFuture<?> future = executorService.scheduleAtFixedRate(new Poll(), 0, 500, TimeUnit.MILLISECONDS);
/* Wait till 60 sec timeout */
try {
future.get(60, TimeUnit.SECONDS);
} catch (TimeoutException e) {
scheduledFuture.cancel(false);
// Take action on timeout
}
私の提案は次のとおりです(質問で言及されているすべてのケースを処理できると思います)。
_public class RepeatedScheduled implements Runnable {
private int repeatCounter = -1;
private boolean infinite;
private ScheduledExecutorService ses;
private long initialDelay;
private long delay;
private TimeUnit unit;
private final Runnable command;
private Future<?> control;
public RepeatedScheduled(ScheduledExecutorService ses, Runnable command,
long initialDelay, long delay, TimeUnit unit) {
this.ses = ses;
this.initialDelay = initialDelay;
this.delay = delay;
this.unit = unit;
this.command = command;
this.infinite = true;
}
public RepeatedScheduled(ScheduledExecutorService ses, Runnable command,
long initialDelay, long delay, TimeUnit unit, int maxExecutions) {
this(ses, command, initialDelay, delay, unit);
this.repeatCounter = maxExecutions;
this.infinite = false;
}
public Future<?> submit() {
// We submit this, not the received command
this.control = this.ses.scheduleWithFixedDelay(this,
this.initialDelay, this.delay, this.unit);
return this.control;
}
@Override
public synchronized void run() {
if ( !this.infinite ) {
if ( this.repeatCounter > 0 ) {
this.command.run();
this.repeatCounter--;
} else {
this.control.cancel(false);
}
} else {
this.command.run();
}
}
}
_
さらに、外部のパーティがsubmit()
メソッドによって返されたFuture
からすべてを停止できるようにします。
使用法:
_Runnable MyRunnable = ...;
// Repeat 20 times
RepeatedScheduled rs = new RepeatedScheduled(
MySes, MyRunnable, 33, 44, TimeUnit.SECONDS, 20);
Future<?> MyControl = rs.submit();
...
_
最初のアプローチは問題ないようです。 mode
オブジェクトをコンストラクタに渡すことで(または実行する必要がある最大回数として-1を渡すことで)両方のタイプのランナブルを組み合わせ、このモードを使用してランナブルをキャンセルする必要があるかどうかを判断できます:
private class DoSomethingNTimesTask implements Runnable{
private int count = 0;
private final int limit;
/**
* Constructor for no limit
*/
private DoSomethingNTimesTask() {
this(-1);
}
/**
* Constructor allowing to set a limit
* @param limit the limit (negative number for no limit)
*/
private DoSomethingNTimesTask(int limit) {
this.limit = limit;
}
@Override
public void run(){
doSomething();
count++;
if(limit >= 0 && count > limit){
// Cancel the scheduling
}
}
}
スケジュールされたフューチャーをタスクに渡してキャンセルする必要があります。そうしないと、例外がスローされる可能性があります。
私はまったく同じ機能を探していて、org.springframework.scheduling.Trigger
。
以下は完全なテスト例です(コードのフラッドが多すぎる場合は申し訳ありません)applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util/ http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<bean id="blockingTasksScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value="10" />
</bean>
<task:scheduler id="deftaskScheduler" pool-size="10" />
</beans>
Java
package com.alz.springTests.schedulerTest;
import Java.time.LocalDateTime;
import Java.time.ZoneId;
import Java.time.temporal.ChronoUnit;
import Java.util.Date;
import Java.util.concurrent.ScheduledThreadPoolExecutor;
import Java.util.concurrent.ThreadLocalRandom;
import Java.util.concurrent.atomic.AtomicInteger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
public class ScheduledTest {
private static ApplicationContext applicationContext;
private static TaskScheduler taskScheduler;
private static final class SelfCancelableTask implements Runnable, Trigger {
Date creationTime = new Date();
AtomicInteger counter = new AtomicInteger(0);
private volatile boolean shouldStop = false;
private int repeatInterval = 3; //seconds
@Override
public void run() {
log("task: run started");
// simulate "doing job" started
int sleepTimeMs = ThreadLocalRandom.current().nextInt(500, 2000+1);
log("will sleep " + sleepTimeMs + " ms");
try {
Thread.sleep(sleepTimeMs);
} catch (InterruptedException e) {
e.printStackTrace();
}
// "doing job" finished
int i = counter.incrementAndGet();
if (i > 5) { //cancel myself
logErr("Attempts exceeded, will mark as shouldStop");
shouldStop = true;
} else {
log("task: executing cycle #"+i);
}
}
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
log("nextExecutionTime: triggerContext.lastActualExecutionTime() " + triggerContext.lastActualExecutionTime());
log("nextExecutionTime: triggerContext.lastCompletionTime() " + triggerContext.lastCompletionTime());
log("nextExecutionTime: triggerContext.lastScheduledExecutionTime() " + triggerContext.lastScheduledExecutionTime());
if (shouldStop)
return null;
if (triggerContext.lastCompletionTime() == null) {
LocalDateTime ldt = creationTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().plus(repeatInterval, ChronoUnit.SECONDS);
return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
} else {
LocalDateTime ldt = triggerContext.lastCompletionTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().plus(repeatInterval, ChronoUnit.SECONDS);
return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
}
}
}
private static void log(String log) {
System.out.printf("%s [%s] %s\r\n", LocalDateTime.now(), Thread.currentThread(), log);
}
private static void logErr(String log) {
System.err.printf("%s [%s] %s\r\n", LocalDateTime.now(), Thread.currentThread(), log);
}
public static void main(String[] args) {
log("main: Stated...");
applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
taskScheduler = (TaskScheduler) applicationContext.getBean("blockingTasksScheduler");
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = ((ThreadPoolTaskScheduler)taskScheduler).getScheduledThreadPoolExecutor();
SelfCancelableTask selfCancelableTask = new SelfCancelableTask();
taskScheduler.schedule(selfCancelableTask, selfCancelableTask);
int waitAttempts = 0;
while (waitAttempts < 30) {
log("scheduledPool pending tasks: " + scheduledThreadPoolExecutor.getQueue().size());
try {
Thread.sleep(1*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
waitAttempts++;
}
log("main: Done!");
}
}