Java ExecutorService 태스크의 예외 처리


213

ThreadPoolExecutor고정 된 수의 스레드로 많은 수의 무거운 작업을 실행 하기 위해 Java 클래스 를 사용하려고 합니다. 각 작업에는 예외로 인해 실패 할 수있는 여러 위치가 있습니다.

하위 클래스를 만들었고 작업을 실행하는 동안 발생하지 않은 예외를 제공 해야하는 메소드를 ThreadPoolExecutor재정의했습니다 afterExecute. 그러나 나는 그것이 효과가있는 것처럼 보이지 않습니다.

예를 들면 다음과 같습니다.

public class ThreadPoolErrors extends ThreadPoolExecutor {
    public ThreadPoolErrors() {
        super(  1, // core threads
                1, // max threads
                1, // timeout
                TimeUnit.MINUTES, // timeout units
                new LinkedBlockingQueue<Runnable>() // work queue
        );
    }

    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if(t != null) {
            System.out.println("Got an error: " + t);
        } else {
            System.out.println("Everything's fine--situation normal!");
        }
    }

    public static void main( String [] args) {
        ThreadPoolErrors threadPool = new ThreadPoolErrors();
        threadPool.submit( 
                new Runnable() {
                    public void run() {
                        throw new RuntimeException("Ouch! Got an error.");
                    }
                }
        );
        threadPool.shutdown();
    }
}

이 프로그램의 결과는 "모든 것이 정상입니다. 상황은 정상입니다!" 스레드 풀에 제출 된 유일한 Runnable에서 예외가 발생하더라도. 여기서 무슨 일이 일어나고 있는지에 대한 단서가 있습니까?

감사!


당신은 그 일의 미래, 무슨 일이 있었는지 물어 보지 않았습니다. 전체 서비스 실행기 또는 프로그램이 중단되지 않습니다. 예외가 포착되어 ExecutionException에 의해 랩핑됩니다. future.get ()을 호출하면 그는 다시 생각할 것입니다. 추신 : runnable이 잘못 완료 된 경우에도 future.isDone () [실제 API 이름을 읽으십시오]이 true를 반환합니다. 작업이 실제로 이루어지기 때문입니다.
Jai Pandit

답변:


156

로부터 문서 :

참고 : 작업이 명시 적으로 또는 제출과 같은 메서드를 통해 작업 (예 : FutureTask)에 포함 된 경우 이러한 작업 개체는 계산 예외를 포착 및 유지하므로 갑작스러운 종료를 일으키지 않으며 내부 예외는이 메서드로 전달되지 않습니다. .

Runnable을 제출하면 미래에 포장됩니다.

afterExecute는 다음과 같아야합니다.

public final class ExtendedExecutor extends ThreadPoolExecutor {

    // ...

    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (t == null && r instanceof Future<?>) {
            try {
                Future<?> future = (Future<?>) r;
                if (future.isDone()) {
                    future.get();
                }
            } catch (CancellationException ce) {
                t = ce;
            } catch (ExecutionException ee) {
                t = ee.getCause();
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
        if (t != null) {
            System.out.println(t);
        }
    }
}

7
고마워, 나는이 솔루션을 사용하게되었습니다. 또한 누군가 관심이있는 경우 : 다른 사람들은 ExecutorService를 서브 클래스 화하지 말 것을 제안했지만 모든 작업이 종료되기를 기다리는 대신 반환 된 모든 선물에 대해 get ()을 호출하는 대신 작업을 완료하고 싶었 기 때문에 어쨌든했습니다. .
Tom

1
집행자를 서브 클래 싱하는 또 다른 방법은 FutureTask를 서브 클래 싱하고 '완료'방법을 무시하는 것입니다

1
Tom >> ExecutorService를 서브 클래 싱 한 샘플 스 니펫 코드를 게시하여 완료된 작업을 모니터링 할 수 있습니까?
jagamot

1
afterExecute에 패키지 개인 개체가 포함되어 있고 던질 수있는 개체에 액세스 할 수 없으므로 ComplableFuture.runAsync를 사용하는 경우이 답변이 작동하지 않습니다. 나는 전화를 감싸서 해결했다. 아래 답변을 참조하십시오.
mmm

2
미래를 사용하여 완료되었는지 확인해야 future.isDone()합니까? 이 완료 된 afterExecute후에 실행 되므로 항상을 반환 Runnable한다고 가정 future.isDone()합니다 true.
Searene

248

경고 :이 솔루션은 호출 스레드를 차단합니다.


작업에서 발생한 예외를 처리하려면 일반적으로보다 사용하는 Callable것이 좋습니다 Runnable.

Callable.call() 확인 된 예외를 throw 할 수 있으며 호출 된 스레드로 다시 전파됩니다.

Callable task = ...
Future future = executor.submit(task);
try {
   future.get();
} catch (ExecutionException ex) {
   ex.getCause().printStackTrace();
}

경우 Callable.call()예외를 throw, 이것은에 싸여됩니다 ExecutionException에 의해 발생합니다 Future.get().

이것은 서브 클래 싱보다 훨씬 바람직 할 것 ThreadPoolExecutor입니다. 또한 예외가 복구 가능한 경우 작업을 다시 제출할 수있는 기회를 제공합니다.


5
> Callable.call ()은 확인 된 예외를 던질 수 있으며, 예외는 호출 스레드로 다시 전파됩니다 . 던져진 예외는 future.get()오버로드 된 버전이 호출 된 경우에만 호출 스레드로 전파됩니다 .
nhylated

16
완벽하지만 작업을 병렬로 실행하고 실행을 차단하지 않으려면 어떻게해야합니까?
Grigory Kislin

43
이 솔루션을 사용하지 마십시오. ExecutorService 사용의 전체 목적을 위반하기 때문입니다. ExecutorService는 백그라운드에서 작업을 실행할 수있는 비동기 실행 메커니즘입니다. 실행 직후 future.get ()을 호출하면 작업이 완료 될 때까지 호출 스레드를 차단합니다.
user1801374

2
이 솔루션은 등급이 너무 높아서는 안됩니다. 하는 Future.get ()는 동 기적으로 작동하고 실행 가능한 또는 호출 가능이 실행 된 때까지 차단 역할을하고 패배 위의 집행자 서비스 사용의 목적을 언급 한 바와 같이
슈퍼 한스

2
#nhylated가 지적했듯이 jdk BUG가 필요합니다. Future.get ()이 호출되지 않으면 Callable의 포착되지 않은 예외는 자동으로 무시됩니다. 매우 나쁜 디자인 .... 라이브러리가 이것을 사용하고 jdk가 예외를 자동으로 무시하는 데 1 일 이상을 보냈습니다. 그리고 이것은 여전히 ​​jdk12에 있습니다.
벤 지앙

18

이 동작에 대한 설명은 afterExecute 에 대한 javadoc에 있습니다 .

참고 : 작업이 명시 적으로 또는 제출과 같은 메서드를 통해 작업 (예 : FutureTask)에 포함 된 경우 이러한 작업 개체는 계산 예외를 포착 및 유지하므로 갑작스러운 종료를 일으키지 않으며 내부 예외는이 메서드로 전달되지 않습니다. .


10

실행기에 제출 된 제공된 실행 파일을 래핑하여 해결했습니다.

CompletableFuture.runAsync(() -> {
        try {
              runnable.run();
        } catch (Throwable e) {
              Log.info(Concurrency.class, "runAsync", e);
        }
}, executorService);

3
whenComplete()방법을 사용하여 가독성을 향상시킬 수 있습니다 CompletableFuture.
Eduard Wirch

@EduardWirch이 작동하지만 whenComplete ()
Akshat

7

모든 예외를 삼키고 기록 하는 jcabi-logVerboseRunnable 클래스를 사용 하고 있습니다. 예를 들어 매우 편리합니다.

import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
  new VerboseRunnable(
    Runnable() {
      public void run() { 
        // the code, which may throw
      }
    },
    true // it means that all exceptions will be swallowed and logged
  ),
  1, 1, TimeUnit.MILLISECONDS
);

3

또 다른 해결책은 ManagedTaskManagedTaskListener 를 사용하는 것 입니다.

ManagedTask 인터페이스를 구현 하는 Callable 또는 Runnable 이 필요합니다 .

이 메소드 getManagedTaskListener는 원하는 인스턴스를 반환합니다.

public ManagedTaskListener getManagedTaskListener() {

그리고 ManagedTaskListener 에서taskDone 메소드 .

@Override
public void taskDone(Future<?> future, ManagedExecutorService executor, Object task, Throwable exception) {
    if (exception != null) {
        LOGGER.log(Level.SEVERE, exception.getMessage());
    }
}

에 대한 자세한 내용은 관리 작업 수명주기와 청취자 .


2

이 작동합니다

  • SingleThreadExecutor에서 파생되었지만 쉽게 조정할 수 있습니다.
  • Java 8 람다 코드, 수정하기 쉬운

단일 스레드로 Executor를 작성하여 많은 작업을 수행 할 수 있습니다. 현재 실행이 다음 다음으로 시작될 때까지 기다립니다.

주의하지 않은 오류 또는 예외가 발생한 경우 uncaughtExceptionHandler 를 포착합니다.

공개 최종 클래스 SingleThreadExecutorWithExceptions {

    공개 정적 ExecutorService newSingleThreadExecutorWithExceptions (최종 Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {

        ThreadFactory 팩토리 = (실행 가능한 실행 가능)-> {
            final Thread newThread = 새 스레드 (실행 가능, "SingleThreadExecutorWithExceptions");
            newThread.setUncaughtExceptionHandler ((최종 스레드 caugthThread, 최종 Throwable throwable)-> {
                uncaughtExceptionHandler.uncaughtException (caugthThread, throwable);
            });
            newThread를 반환;
        };
        새로운 FinalizableDelegatedExecutorService를 반환합니다
                (새로운 ThreadPoolExecutor (1, 1,
                        0L, TimeUnit.MILLISECONDS,
                        새로운 LinkedBlockingQueue (),
                        공장){


                    protected void afterExecute (실행 가능 실행 가능, Throwable Throwable) {
                        super.afterExecute (실행 가능, 던지기 가능);
                        if (throwable == null && runnable instance of Future) {
                            {
                                미래 미래 = (미래) 실행 가능;
                                if (future.isDone ()) {
                                    future.get ();
                                }
                            } catch (CancellationException ce) {
                                던질 수있는 = ce;
                            } catch (ExecutionException ee) {
                                throwable = ee.getCause ();
                            } catch (InterruptedException 즉) {
                                Thread.currentThread (). interrupt (); // 무시 / 리셋
                            }
                        }
                        if (throwable! = null) {
                            uncaughtExceptionHandler.uncaughtException (Thread.currentThread (), throwable);
                        }
                    }
                });
    }



    개인 정적 클래스 FinalizableDelegatedExecutorService
            DelegatedExecutorService {
        FinalizableDelegatedExecutorService (ExecutorService executor) {
            슈퍼 (실행자);
        }
        protected void finalize () {
            super.shutdown ();
        }
    }

    / **
     * ExecutorService 메소드 만 노출하는 랩퍼 클래스
     ExecutorService 구현의 *
     * /
    개인 정적 클래스 DelegatedExecutorService는 AbstractExecutorService를 확장합니다 {
        개인 최종 ExecutorService e;
        DelegatedExecutorService (ExecutorService executor) {e = 집행자; }
        공공 무효 실행 (실행 가능한 명령) {e.execute (명령); }
        공공 무효 종료 () {e.shutdown (); }
        공개리스트 shutdownNow () {return e.shutdownNow (); }
        공개 부울 isShutdown () {return e.isShutdown (); }
        공개 부울 isTerminated () {return e.isTerminated (); }
        public boolean awaitTermination (긴 타임 아웃, TimeUnit 단위)
                InterruptedException {
            반환 e.awaitTermination (시간 초과, 단위);
        }
        공개 미래 제출 (실행 가능한 작업) {
            반환 e. 제출 (작업);
        }
        공개 미래 제출 (통화 가능한 작업) {
            반환 e. 제출 (작업);
        }
        공개 미래 제출 (실행 가능한 작업, T 결과) {
            반환 e. 제출 (작업, 결과);
        }
        공개 목록> invokeAll (Collection> tasks)
                InterruptedException {
            반환 e.invokeAll (작업);
        }
        공개 목록> invokeAll (Collection> 작업,
                                             긴 시간 초과, 시간 단위 단위)
                InterruptedException {
            반환 e.invokeAll (작업, 시간 초과, 단위);
        }
        공개 T invokeAny (Collection> tasks)
                InterruptedException, ExecutionException {
            반환 e.invokeAny (작업);
        }
        공개 T invokeAny (Collection> 작업,
                               긴 시간 초과, 시간 단위 단위)
                InterruptedException, ExecutionException, TimeoutException {을 발생시킵니다.
            반환 e.invokeAny (작업, 시간 초과, 단위);
        }
    }



    개인 SingleThreadExecutorWithExceptions () {}
}

"가비지 수집기가 나중에 수집 할 때"또는 "Thread, dunno의 경우에는 그렇지 않음"이라고 만 불리기 때문에 finalize를 사용하는 것은 불행하게도 약간 불안정합니다.
rogerdpack

1

작업 실행을 모니터링하려면 1 개 또는 2 개의 스레드 (로드에 따라 더 많을 수 있음)를 스핀하고이를 사용하여 ExecutionCompletionService 랩퍼에서 작업을 수행 할 수 있습니다.


0

ExecutorService외부 소스에서 온 경우 (즉, 서브 클래스 ThreadPoolExecutor및 재정의가 불가능한 경우 afterExecute()) 동적 프록시를 사용하여 원하는 동작을 수행 할 수 있습니다.

public static ExecutorService errorAware(final ExecutorService executor) {
    return (ExecutorService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
            new Class[] {ExecutorService.class},
            (proxy, method, args) -> {
                if (method.getName().equals("submit")) {
                    final Object arg0 = args[0];
                    if (arg0 instanceof Runnable) {
                        args[0] = new Runnable() {
                            @Override
                            public void run() {
                                final Runnable task = (Runnable) arg0;
                                try {
                                    task.run();
                                    if (task instanceof Future<?>) {
                                        final Future<?> future = (Future<?>) task;

                                        if (future.isDone()) {
                                            try {
                                                future.get();
                                            } catch (final CancellationException ce) {
                                                // Your error-handling code here
                                                ce.printStackTrace();
                                            } catch (final ExecutionException ee) {
                                                // Your error-handling code here
                                                ee.getCause().printStackTrace();
                                            } catch (final InterruptedException ie) {
                                                Thread.currentThread().interrupt();
                                            }
                                        }
                                    }
                                } catch (final RuntimeException re) {
                                    // Your error-handling code here
                                    re.printStackTrace();
                                    throw re;
                                } catch (final Error e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                }
                            }
                        };
                    } else if (arg0 instanceof Callable<?>) {
                        args[0] = new Callable<Object>() {
                            @Override
                            public Object call() throws Exception {
                                final Callable<?> task = (Callable<?>) arg0;
                                try {
                                    return task.call();
                                } catch (final Exception e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                } catch (final Error e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                }
                            }
                        };
                    }
                }
                return method.invoke(executor, args);
            });
}

0

이는입니다 AbstractExecutorService :: submit당신의 포장됩니다 runnableRunnableFuture(아무것도하지만를 FutureTask아래와 같이)

AbstractExecutorService.java

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null); /////////HERE////////
    execute(ftask);
    return ftask;
}

그런 다음 execute그것을 전달합니다 WorkerWorker.run()아래 호출합니다.

ThreadPoolExecutor.java

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();           /////////HERE////////
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

마지막으로 task.run();위의 코드 호출에서을 호출 FutureTask.run()합니다. 다음은 예외 처리기 코드입니다.이 때문에 예상 한 예외가 발생하지 않습니다.

class FutureTask<V> implements RunnableFuture<V>

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {   /////////HERE////////
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

0

이것은 mmm의 솔루션과 비슷하지만 좀 더 이해하기 쉽습니다. 작업이 run () 메소드를 감싸는 추상 클래스를 확장하도록하십시오.

public abstract Task implements Runnable {

    public abstract void execute();

    public void run() {
      try {
        execute();
      } catch (Throwable t) {
        // handle it  
      }
    }
}


public MySampleTask extends Task {
    public void execute() {
        // heavy, error-prone code here
    }
}

-4

ThreadPoolExecutor를 서브 클래 싱하는 대신 새 Thread를 생성하고 UncaughtExceptionHandler를 제공 하는 ThreadFactory 인스턴스를 제공합니다.


3
나는 이것을 시도했지만 uncaughtException 메소드는 결코 호출되지 않는 것 같습니다. ThreadPoolExecutor 클래스의 작업자 스레드가 예외를 잡기 때문이라고 생각합니다.
Tom

5
ExecutorService의 submit 메소드가 미래에 Callable / Runnable을 랩핑하고 있기 때문에 uncaughtException 메소드는 호출되지 않습니다. 예외가 캡처되고 있습니다.
Emil 앉아
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.