일정 횟수의 실행 후 반복 실행되도록 예약 된 Runnable을 중지하는 방법
상태
Runnable이 있습니다. 나는 실행을위한 일정이의 Runnable이있는 ScheduledExecutorService를 사용하는 클래스가 scheduleWithFixedDelay을 .
골
나는 고정 지연 실행을위한 실행 가능한 일정을이 클래스 변경하려는 중 무기한를, 또는 그것은 생성자에 전달되는 몇 가지 매개 변수에 따라 특정 횟수를 실행할 때까지.
가능하면 동일한 Runnable을 사용하고 싶습니다. 개념적으로 "실행"되어야하는 것과 동일한 것입니다.
가능한 접근법
접근법 # 1
두 개의 Runnable이 있습니다. 하나는 여러 번 실행 (카운트 유지) 한 후 일정을 취소하는 것이고 다른 하나는 그렇지 않은 것입니다.
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을 갖고 싶습니다. 일정을 Runnable에 묶는 것은 잘못된 느낌입니다. 이것에 대해 어떻게 생각하십니까?
접근법 # 2
주기적으로 실행하려는 코드의 실행을위한 단일 Runnable을 갖습니다. 첫 번째 Runnable이 실행 된 횟수를 확인하고 일정량에 도달하면 취소하는 별도의 예약 된 실행 가능 파일이 있습니다. 비동기식이므로 정확하지 않을 수 있습니다. 조금 번거 롭습니다. 이것에 대해 어떻게 생각하십니까?
접근법 # 3
ScheduledExecutorService를 확장하고 "scheduleWithFixedDelayNTimes"메소드를 추가하십시오. 아마도 그러한 클래스가 이미 존재합니까? 현재 저는 Executors.newSingleThreadScheduledExecutor();
ScheduledExecutorService 인스턴스를 가져 오는 데 사용 하고 있습니다. 아마도 확장 된 ScheduledExecutorService를 인스턴스화하기 위해 유사한 기능을 구현해야 할 것입니다. 이것은 까다로울 수 있습니다. 이것에 대해 어떻게 생각하십니까?
스케줄러 접근 방식 없음 [편집]
스케줄러를 사용할 수 없습니다. 대신 다음과 같은 것을 가질 수 있습니다.
for(int i = 0; i < numTimesToRun; i++){
doSomething();
Thread.sleep(delay);
}
그리고 일부 스레드에서 실행하십시오. 그것에 대해 어떻게 생각하세요? 잠재적으로 여전히 runnable을 사용하고 run 메서드를 직접 호출 할 수 있습니다.
어떤 제안이라도 환영합니다. 내 목표를 달성하기위한 "모범 사례"방법을 찾기위한 토론을 찾고 있습니다.
Future에서 cancel () 메서드를 사용할 수 있습니다. scheduleAtFixedRate 의 javadocs에서
Otherwise, the task will only terminate via cancellation or termination of the executor
다음은 원본이 실행 된 횟수를 추적하고 N 번 실행 한 후 취소하는 다른 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
) 에서 인용 :
지정된 초기 지연 후 먼저 활성화되고 이후에 한 실행의 종료와 다음 실행의 시작 사이에 지정된 지연으로 활성화되는 주기적 작업을 생성하고 실행합니다. 태스크 실행에서 예외가 발생하면 후속 실행이 억제됩니다. 그렇지 않으면 작업은 실행 프로그램의 취소 또는 종료를 통해서만 종료됩니다.
따라서 가장 쉬운 방법은 "예외를 던지는 것"입니다 (나쁜 습관으로 간주 되더라도).
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
자체에 맡긴다 는 점을 제외하면 가장 깨끗한 것 같습니다 . 이것은 이것과 관련되어서는 안되며, 대신 반복은 스케줄링을 처리하는 클래스의 매개 변수 여야합니다. 이를 위해 .NET 용 새 실행기 클래스를 도입하는 다음 디자인을 제안합니다 Runnables
. 이 클래스는 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)
}
}
}
}
}
}
}
공정이 될하려면 반복을 관리하는 논리로 여전히 하지만 it'a은 받는 사람이 완전히 내부 , 반면 스케줄링 통과 작업 스케줄링의 자연과하지 문제 자체에 있습니다. 또한이 문제 는 실행될 때마다 콜백을 제공하여 원하는 경우 스케줄러로 쉽게 이동할 수 있습니다 . 이로 인해 코드가 약간 복잡해지고 s 맵 과 해당 반복 을 유지해야 할 필요성이 생기기 때문에 클래스에 카운터를 유지하기로 결정했습니다 . Runnable
Runnable
MaxNScheduler
Runnable
RunnableWrapper.run
RunnableWrapper
RunnableWrapper
또한 self를 설정할 때 래퍼에 동기화를 추가했습니다. 이것은 이론적으로 실행이 완료 될 때 self가 아직 할당되지 않았을 수 있으므로 필요합니다 (상당히 이론적 인 시나리오이지만 한 번만 반복 할 수 있음).
취소는를 던지지 않고 정상적으로 처리 InterruptedException
되며 취소가 실행되기 전에 다른 라운드가 예약 된 경우 RunnableWrapper
는 기본을 호출하지 않습니다 Runnable
.
첫 번째 접근 방식은 괜찮아 보입니다. 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
}
}
}
작업이 자체적으로 취소되도록하려면 예정된 미래를 작업에 전달해야합니다. 그렇지 않으면 예외가 발생할 수 있습니다.
내 제안은 다음과 같습니다 (질문에 언급 된 모든 경우를 처리한다고 생각합니다).
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();
}
}
}
또한 외부 당사자가 메서드에 Future
의해 반환 된 모든 것을 중지 할 수 있습니다 submit()
.
용법:
Runnable MyRunnable = ...;
// Repeat 20 times
RepeatedScheduled rs = new RepeatedScheduled(
MySes, MyRunnable, 33, 44, TimeUnit.SECONDS, 20);
Future<?> MyControl = rs.submit();
...
특정 제한 시간까지 폴링과 같은 사용 사례의 경우 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
}
나는 똑같은 기능을 찾고 있었고 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>
자바
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!");
}
}
'programing tip' 카테고리의 다른 글
커밋 할 때마다 git에 파일을 추가해야합니까? (0) | 2020.12.31 |
---|---|
비표준 위치에서 SSL 지원으로 Python 빌드 (0) | 2020.12.31 |
Google 크롬을 사용하여 HTML 페이지에 포함 된 자바 스크립트 디버깅 및 편집 (0) | 2020.12.31 |
getString ()과 getResources.getString ()의 차이점 (0) | 2020.12.31 |
사전 업데이트 시퀀스 요소 # 0의 길이는 3입니다. (0) | 2020.12.31 |