자바 : 특정 코드 블록에 시간 초과 설정?
일부 코드 블록이 허용 가능한 것보다 오래 실행 된 후 Java가 예외를 발생 시키도록 강제 할 수 있습니까?
예, 그러나 일반적으로 임의의 코드 줄에서 다른 스레드를 강제로 인터럽트하는 것은 매우 나쁜 생각입니다. 프로세스를 종료하려는 경우에만이 작업을 수행합니다.
당신이 할 수 Thread.interrupt()
있는 일은 일정 시간이 지난 후 작업 에 사용 하는 것입니다. 그러나 코드가이를 확인하지 않으면 작동하지 않습니다. ExecutorService는 다음과 같이 쉽게 만들 수 있습니다.Future.cancel(true)
코드가 자체 시간을 정하고 필요할 때 중지하는 것이 훨씬 좋습니다.
이 작업을 수행하는 가장 간단한 방법은 다음과 같습니다.
final Runnable stuffToDo = new Thread() {
@Override
public void run() {
/* Do stuff here. */
}
};
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future future = executor.submit(stuffToDo);
executor.shutdown(); // This does not cancel the already-scheduled task.
try {
future.get(5, TimeUnit.MINUTES);
}
catch (InterruptedException ie) {
/* Handle the interruption. Or ignore it. */
}
catch (ExecutionException ee) {
/* Handle the error. Or ignore it. */
}
catch (TimeoutException te) {
/* Handle the timeout. Or ignore it. */
}
if (!executor.isTerminated())
executor.shutdownNow(); // If you want to stop the code that hasn't finished.
또는 TimeLimitedCodeBlock 클래스를 만들어이 기능을 래핑 한 다음 필요한 곳에서 다음과 같이 사용할 수 있습니다.
new TimeLimitedCodeBlock(5, TimeUnit.MINUTES) { @Override public void codeBlock() {
// Do stuff here.
}}.run();
다른 답변 중 일부를 단일 유틸리티 메서드로 컴파일했습니다.
public class TimeLimitedCodeBlock {
public static void runWithTimeout(final Runnable runnable, long timeout, TimeUnit timeUnit) throws Exception {
runWithTimeout(new Callable<Object>() {
@Override
public Object call() throws Exception {
runnable.run();
return null;
}
}, timeout, timeUnit);
}
public static <T> T runWithTimeout(Callable<T> callable, long timeout, TimeUnit timeUnit) throws Exception {
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future<T> future = executor.submit(callable);
executor.shutdown(); // This does not cancel the already-scheduled task.
try {
return future.get(timeout, timeUnit);
}
catch (TimeoutException e) {
//remove this if you do not want to cancel the job in progress
//or set the argument to 'false' if you do not want to interrupt the thread
future.cancel(true);
throw e;
}
catch (ExecutionException e) {
//unwrap the root cause
Throwable t = e.getCause();
if (t instanceof Error) {
throw (Error) t;
} else if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new IllegalStateException(t);
}
}
}
}
이 유틸리티 메서드를 사용하는 샘플 코드 :
public static void main(String[] args) throws Exception {
final long startTime = System.currentTimeMillis();
log(startTime, "calling runWithTimeout!");
try {
TimeLimitedCodeBlock.runWithTimeout(new Runnable() {
@Override
public void run() {
try {
log(startTime, "starting sleep!");
Thread.sleep(10000);
log(startTime, "woke up!");
}
catch (InterruptedException e) {
log(startTime, "was interrupted!");
}
}
}, 5, TimeUnit.SECONDS);
}
catch (TimeoutException e) {
log(startTime, "got timeout!");
}
log(startTime, "end of main method!");
}
private static void log(long startTime, String msg) {
long elapsedSeconds = (System.currentTimeMillis() - startTime);
System.out.format("%1$5sms [%2$16s] %3$s\n", elapsedSeconds, Thread.currentThread().getName(), msg);
}
내 컴퓨터에서 샘플 코드를 실행 한 결과 :
0ms [ main] calling runWithTimeout!
13ms [ pool-1-thread-1] starting sleep!
5015ms [ main] got timeout!
5016ms [ main] end of main method!
5015ms [ pool-1-thread-1] was interrupted!
시간을 측정하려는 테스트 코드 인 경우 time
속성을 사용할 수 있습니다 .
@Test(timeout = 1000)
public void shouldTakeASecondOrLess()
{
}
프로덕션 코드 인 경우 간단한 메커니즘이 없으며 사용하는 솔루션은 코드를 시간 제한으로 변경할 수 있는지 여부에 따라 다릅니다.
시간이 지정된 코드를 변경할 수있는 경우 간단한 방법은 시간이 지정된 코드가 시작 시간을 기억하고 주기적으로 현재 시간을 기억하도록하는 것입니다. 예
long startTime = System.currentTimeMillis();
// .. do stuff ..
long elapsed = System.currentTimeMillis()-startTime;
if (elapsed>timeout)
throw new RuntimeException("tiomeout");
코드 자체가 시간 초과를 확인할 수없는 경우 다른 스레드에서 코드를 실행하고 완료 또는 시간 초과를 기다릴 수 있습니다.
Callable<ResultType> run = new Callable<ResultType>()
{
@Override
public ResultType call() throws Exception
{
// your code to be timed
}
};
RunnableFuture future = new FutureTask(run);
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(future);
ResultType result = null;
try
{
result = future.get(1, TimeUnit.SECONDS); // wait 1 second
}
catch (TimeoutException ex)
{
// timed out. Try to stop the code if possible.
future.cancel(true);
}
service.shutdown();
}
두 가지 옵션을 제안 할 수 있습니다.
메서드 내에서 외부 이벤트를 기다리지 않고 루핑 중이라고 가정하고 로컬 필드를 추가하고 루프를 돌 때마다 시간을 테스트합니다.
void method() { long endTimeMillis = System.currentTimeMillis() + 10000; while (true) { // method logic if (System.currentTimeMillis() > endTimeMillis) { // do some clean-up return; } } }
스레드에서 메서드를 실행하고 호출자를 10 초로 계산합니다.
Thread thread = new Thread(new Runnable() { @Override public void run() { method(); } }); thread.start(); long endTimeMillis = System.currentTimeMillis() + 10000; while (thread.isAlive()) { if (System.currentTimeMillis() > endTimeMillis) { // set an error flag break; } try { Thread.sleep(500); } catch (InterruptedException t) {} }
이 접근 방식의 단점은 method ()가 값을 직접 반환 할 수 없으며 해당 값을 반환하도록 인스턴스 필드를 업데이트해야한다는 것입니다.
편집 : Peter Lawrey가 완전히 옳습니다 : 스레드를 중단하는 것만 큼 간단하지 않으며 (내 원래 제안) Executors & Callables는 매우 유용합니다 ...
스레드를 중단하는 대신 시간 초과에 도달하면 Callable에 변수를 설정할 수 있습니다. 콜 러블은 작업 실행의 적절한 지점에서이 변수를 확인하여 중지 할시기를 알아야합니다.
Callables는 Future를 반환하며,이를 통해 Future의 결과를 '얻으려고'할 때 시간 제한을 지정할 수 있습니다. 이 같은:
try {
future.get(timeoutSeconds, TimeUnit.SECONDS)
} catch(InterruptedException e) {
myCallable.setStopMeAtAppropriatePlace(true);
}
Future.get, Executors 및 Callable을 참조하십시오 ...
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html
프레임 워크 나 API를 사용하지 않고 매우 간단한 솔루션을 만들었습니다. 이것은 더 우아하고 이해하기 쉬워 보입니다. 이 클래스를 TimeoutBlock이라고합니다.
public class TimeoutBlock {
private final long timeoutMilliSeconds;
private long timeoutInteval=100;
public TimeoutBlock(long timeoutMilliSeconds){
this.timeoutMilliSeconds=timeoutMilliSeconds;
}
public void addBlock(Runnable runnable) throws Throwable{
long collectIntervals=0;
Thread timeoutWorker=new Thread(runnable);
timeoutWorker.start();
do{
if(collectIntervals>=this.timeoutMilliSeconds){
timeoutWorker.stop();
throw new Exception("<<<<<<<<<<****>>>>>>>>>>> Timeout Block Execution Time Exceeded In "+timeoutMilliSeconds+" Milli Seconds. Thread Block Terminated.");
}
collectIntervals+=timeoutInteval;
Thread.sleep(timeoutInteval);
}while(timeoutWorker.isAlive());
System.out.println("<<<<<<<<<<####>>>>>>>>>>> Timeout Block Executed Within "+collectIntervals+" Milli Seconds.");
}
/**
* @return the timeoutInteval
*/
public long getTimeoutInteval() {
return timeoutInteval;
}
/**
* @param timeoutInteval the timeoutInteval to set
*/
public void setTimeoutInteval(long timeoutInteval) {
this.timeoutInteval = timeoutInteval;
}
}
예 :
try {
TimeoutBlock timeoutBlock = new TimeoutBlock(10 * 60 * 1000);//set timeout in milliseconds
Runnable block=new Runnable() {
@Override
public void run() {
//TO DO write block of code to execute
}
};
timeoutBlock.addBlock(block);// execute the runnable block
} catch (Throwable e) {
//catch the exception here . Which is block didn't execute within the time limit
}
FTP 계정에 연결해야 할 때 매우 유용했습니다. 그런 다음 항목을 다운로드하고 업로드하십시오. 때때로 FTP 연결이 중단되거나 완전히 끊어집니다. 이로 인해 전체 시스템이 다운되었습니다. 그리고 나는 그것을 감지하고 그것을 방지하는 방법이 필요했습니다. 그래서 이것을 만들어 사용했습니다. 꽤 잘 작동합니다.
CompletableFuture 방식을 원한다면 다음과 같은 방법을 사용할 수 있습니다.
public MyResponseObject retrieveDataFromEndpoint() {
CompletableFuture<MyResponseObject> endpointCall
= CompletableFuture.supplyAsync(() ->
yourRestService.callEnpoint(withArg1, withArg2));
try {
return endpointCall.get(10, TimeUnit.MINUTES);
} catch (TimeoutException
| InterruptedException
| ExecutionException e) {
throw new RuntimeException("Unable to fetch data", e);
}
}
If you're using spring, you could annotate the method with a @Retryable
so that it retries the method three times if an exception is thrown.
There is a hacky way to do it.
Set some boolean field to indicate whether the work was completed. Then before the block of code, set a timer to run a piece of code after your timeout. The timer will check if the block of code had finished executing, and if not, throw an exception. Otherwise it will do nothing.
The end of the block of code should, of course, set the field to true to indicate the work was done.
Instead of having the task in the new thread and the timer in the main thread, have the timer in the new thread and the task in the main thread:
public static class TimeOut implements Runnable{
public void run() {
Thread.sleep(10000);
if(taskComplete ==false) {
System.out.println("Timed Out");
return;
}
else {
return;
}
}
}
public static boolean taskComplete = false;
public static void main(String[] args) {
TimeOut timeOut = new TimeOut();
Thread timeOutThread = new Thread(timeOut);
timeOutThread.start();
//task starts here
//task completed
taskComplete =true;
while(true) {//do all other stuff }
}
I faced a similar kind of issue where my task was to push a message to SQS within a particular timeout. I used the trivial logic of executing it via another thread and waiting on its future object by specifying the timeout. This would give me a TIMEOUT exception in case of timeouts.
final Future<ISendMessageResult> future =
timeoutHelperThreadPool.getExecutor().submit(() -> {
return getQueueStore().sendMessage(request).get();
});
try {
sendMessageResult = future.get(200, TimeUnit.MILLISECONDS);
logger.info("SQS_PUSH_SUCCESSFUL");
return true;
} catch (final TimeoutException e) {
logger.error("SQS_PUSH_TIMEOUT_EXCEPTION");
}
But there are cases where you can't stop the code being executed by another thread and you get true negatives in that case.
For example - In my case, my request reached SQS and while the message was being pushed, my code logic encountered the specified timeout. Now in reality my message was pushed into the Queue but my main thread assumed it to be failed because of the TIMEOUT exception. This is a type of problem which can be avoided rather than being solved. Like in my case I avoided it by providing a timeout which would suffice in nearly all of the cases.
If the code you want to interrupt is within you application and is not something like an API call then you can simply use
future.cancel(true)
However do remember that java docs says that it does guarantee that the execution will be blocked.
"Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled,or could not be cancelled for some other reason. If successful,and this task has not started when cancel is called,this task should never run. If the task has already started,then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted inan attempt to stop the task."
참고URL : https://stackoverflow.com/questions/5715235/java-set-timeout-on-a-certain-block-of-code
'IT박스' 카테고리의 다른 글
JavaScript가 if 문에서 쉼표를 허용하는 이유는 무엇입니까? (0) | 2020.12.01 |
---|---|
URL에 공백이 있습니까? (0) | 2020.12.01 |
Assert.AreNotEqual과 Assert.AreNotSame의 차이점은 무엇입니까? (0) | 2020.11.30 |
CSS는 개발 환경과 웹 서버에서 다르게 렌더링됩니다. (0) | 2020.11.30 |
WPF TextBox에 이벤트 붙여 넣기 (0) | 2020.11.30 |