Xử lý các ngoại lệ từ các tác vụ Java ExecutorService


213

Tôi đang cố gắng sử dụng Java ThreadPoolExecutor lớp để chạy một số lượng lớn các tác vụ nặng với số lượng luồng cố định. Mỗi nhiệm vụ có nhiều nơi trong đó nó có thể thất bại do ngoại lệ.

Tôi đã phân lớp ThreadPoolExecutorvà tôi đã ghi đè afterExecutephương thức được cho là cung cấp bất kỳ trường hợp ngoại lệ chưa được bắt gặp nào khi chạy một tác vụ. Tuy nhiên, tôi dường như không thể làm cho nó hoạt động.

Ví dụ:

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();
    }
}

Đầu ra từ chương trình này là "Mọi thứ đều ổn - tình hình bình thường!" mặc dù Runnable duy nhất được gửi tới nhóm luồng sẽ ném ngoại lệ. Có manh mối nào cho những gì đang xảy ra ở đây không?

Cảm ơn!


bạn không bao giờ truy vấn Tương lai của nhiệm vụ, những gì đã xảy ra ở đó. Toàn bộ chương trình thực thi dịch vụ hoặc chương trình sẽ không bị sập. Ngoại lệ được bắt và được gói trong ExecutException. Và anh ta sẽ rút lui nếu bạn gọi tương lai.get (). PS: Tương lai.isDone () [Vui lòng đọc tên api thực] sẽ trở lại đúng, ngay cả khi runnable kết thúc một cách sai lầm. Bởi vì nhiệm vụ được thực hiện cho thực tế.
Jai Pandit

Câu trả lời:


156

Từ các tài liệu :

Lưu ý: Khi các hành động được đặt trong các tác vụ (như FutureTask) một cách rõ ràng hoặc thông qua các phương thức như gửi, các đối tượng tác vụ này bắt và duy trì các ngoại lệ tính toán và do đó chúng không gây ra chấm dứt đột ngột và các ngoại lệ bên trong không được truyền cho phương thức này .

Khi bạn gửi Runnable, nó sẽ được bọc trong Tương lai.

AfterExecute của bạn phải giống như thế này:

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
Cảm ơn, tôi đã kết thúc việc sử dụng giải pháp này. Ngoài ra, trong trường hợp bất kỳ ai cũng quan tâm: những người khác đã đề nghị không phân lớp ExecutorService, nhưng tôi vẫn làm vì tôi muốn theo dõi các nhiệm vụ khi chúng hoàn thành thay vì chờ tất cả chúng kết thúc và sau đó gọi get () trên tất cả các Futures đã trả về .
Tom

1
Một cách tiếp cận khác để phân lớp người thực thi là phân lớp FutureTask và ghi đè phương thức 'xong' của nó
nos

1
Tom >> Bạn có thể vui lòng gửi mã đoạn mã mẫu của mình vào nơi bạn đã phân lớp ExecutorService để theo dõi các nhiệm vụ khi chúng hoàn thành ...
jagamot

1
Câu trả lời này sẽ không hoạt động nếu bạn đang sử dụng ComplableFuture.runAsync vì afterExecute sẽ chứa một đối tượng là gói riêng tư và không có cách nào để truy cập có thể ném được. Tôi đã khắc phục nó bằng cách kết thúc cuộc gọi. Xem câu trả lời của tôi dưới đây.
mmm

2
Chúng ta có phải kiểm tra xem tương lai đã hoàn thành future.isDone()chưa? Vì afterExecuteđược chạy sau khi Runnablehoàn thành, tôi giả sử future.isDone()luôn luôn trả về true.
Searene

248

CẢNH BÁO : Cần lưu ý rằng giải pháp này sẽ chặn luồng cuộc gọi.


Nếu bạn muốn xử lý các ngoại lệ được ném bởi tác vụ, thì nói chung nên sử dụng Callabletốt hơn làRunnable .

Callable.call() được phép ném các ngoại lệ được kiểm tra và chúng được truyền trở lại chuỗi gọi:

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

Nếu Callable.call()ném một ngoại lệ, nó sẽ được bọc trong ExecutionExceptionvà ném bởiFuture.get() .

Điều này có khả năng được ưu tiên hơn nhiều so với phân lớp ThreadPoolExecutor. Nó cũng cung cấp cho bạn cơ hội để gửi lại nhiệm vụ nếu ngoại lệ là một trường hợp có thể phục hồi.


5
> Callable.call () được phép ném các ngoại lệ được kiểm tra và chúng được truyền trở lại luồng gọi: Lưu ý rằng ngoại lệ bị ném sẽ chỉ truyền đến luồng gọi nếu future.get()phiên bản bị quá tải của nó được gọi.
nhylated

16
Nó là hoàn hảo, nhưng phải làm gì nếu tôi chạy các tác vụ song song và không muốn chặn thực thi?
Grigory Kislin

43
Đừng sử dụng giải pháp này, vì nó phá vỡ toàn bộ mục đích sử dụng ExecutorService. ExecutorService là một cơ chế thực thi không đồng bộ, có khả năng thực thi các tác vụ trong nền. Nếu bạn gọi tương lai.get () ngay sau khi thực thi, nó sẽ chặn luồng gọi cho đến khi tác vụ kết thúc.
dùng1801374

2
Giải pháp này không nên được đánh giá cao. Future.get () hoạt động đồng bộ và sẽ hoạt động như một trình chặn cho đến khi Runnable hoặc Callable được thực thi và như đã nêu ở trên đánh bại mục đích sử dụng Dịch vụ thực thi
Super Hans

2
Như #nhylated đã chỉ ra, điều này xứng đáng là một BUG jdk. Nếu Future.get () không được gọi, bất kỳ ngoại lệ chưa được phát hiện nào từ Callable sẽ bị bỏ qua trong âm thầm. Thiết kế rất tệ .... chỉ mất hơn 1 ngày để tìm ra một thư viện được sử dụng và jdk âm thầm bỏ qua các ngoại lệ. Và, điều này vẫn tồn tại trong jdk12.
Ben Jiang

18

Giải thích cho hành vi này là đúng trong javadoc cho afterExecute :

Lưu ý: Khi các hành động được đặt trong các tác vụ (như FutureTask) một cách rõ ràng hoặc thông qua các phương thức như gửi, các đối tượng tác vụ này bắt và duy trì các ngoại lệ tính toán và do đó chúng không gây ra chấm dứt đột ngột và các ngoại lệ bên trong không được truyền cho phương thức này .


10

Tôi đã khắc phục nó bằng cách gói runnable được cung cấp gửi cho người thi hành.

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

3
Bạn có thể cải thiện khả năng đọc bằng cách sử dụng whenComplete()phương pháp CompletableFuture.
Eduard Wirch

@EduardWirch điều này hoạt động nhưng bạn không thể trả lại một ngoại lệ từ khiComplete ()
Akshat

7

Tôi đang sử dụng VerboseRunnablelớp từ jcabi-log , nó nuốt tất cả các ngoại lệ và ghi lại chúng. Rất thuận tiện, ví dụ:

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

Một giải pháp khác là sử dụng ManagedTaskManagedTaskListener .

Bạn cần một Callable hoặc Runnable để thực hiện giao diện ManagedTask .

Phương thức getManagedTaskListenertrả về thể hiện bạn muốn.

public ManagedTaskListener getManagedTaskListener() {

Và bạn thực hiện trong ManagedTaskListener các taskDonephương pháp:

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

Thêm chi tiết về vòng đời nhiệm vụ được quản lý và người nghe .


2

Những công việc này

  • Nó có nguồn gốc từ SingleThreadExecutor, nhưng bạn có thể điều chỉnh nó dễ dàng
  • Mã Java 8 lamdas, nhưng dễ sửa

Nó sẽ tạo ra một Executor với một luồng duy nhất, có thể nhận được rất nhiều nhiệm vụ; và sẽ đợi cho hiện tại kết thúc thực hiện bắt đầu với tiếp theo

Trong trường hợp lỗi uncaugth hoặc ngoại lệ các uncaughtExceptionHandler sẽ bắt nó

lớp cuối cùng công khai SingleThreadExecutorWithExceptions {

    công khai tĩnh ExecutorService newSingleThreadExecutorWithExceptions (chủ đề cuối cùng.UncaughtExceptionHandler unsaughtExceptionHandler) {

        Nhà máy ThreadFactory = (Runnable runnable) -> {
            chủ đề cuối cùng newThread = chủ đề mới (runnable, "SingleThreadExecutorWithExceptions");
            newThread.setUncaughtExceptionHandler ((chủ đề cuối cùng caugthThread, có thể ném được cuối cùng) -> {
                unsaughtExceptionHandler.uncaughtException (caugthThread, throwable);
            });
            trả về newThread;
        };
        trả về FinalizableDelegatedExecutorService
                (ThreadPoolExecutor mới (1, 1,
                        0L, TimeUnit.MILLISECONDS,
                        LinkedBlockingQueue (),
                        nhà máy){


                    được bảo vệ void afterExecute (Runnable runnable, throwable throwable) {
                        super.afterExecute (runnable, throwable);
                        if (throwable == null && runnable instanceof Tương lai) {
                            thử {
                                Tương lai tương lai = (Tương lai) runnable;
                                if (tương lai.isDone ()) {
                                    tương lai.get ();
                                }
                            } bắt (CancellingException ce) {
                                ném được = ce;
                            } Catch (ExecutException ee) {
                                throwable = ee.getCause ();
                            } Catch (InterruptedException tức là) {
                                Thread.cienThread (). Interrupt (); // bỏ qua / thiết lập lại
                            }
                        }
                        if (throwable! = null) {
                            unsaughtExceptionHandler.uncaughtException (Thread.cienThread (), có thể ném được);
                        }
                    }
                });
    }



    lớp tĩnh riêng FinalizableDelegatedExecutorService
            mở rộng DelegatedExecutorService {
        FinalizableDelegatedExecutorService (giám đốc điều hành ExecutorService) {
            siêu (chấp hành viên);
        }
        bảo vệ void hoàn thiện () {
            super.shutdown ();
        }
    }

    / **
     * Một lớp bao bọc chỉ hiển thị các phương thức ExecutorService
     * của việc triển khai ExecutorService.
     * /
    lớp tĩnh riêng DelegatedExecutorService mở rộng AbstractExecutorService {
        riêng ExecutorService e;
        DelegatedExecutorService (ExecutorService execor) {e = execor; }
        công khai void void (lệnh Runnable) {e.execute (lệnh); }
        công khai void shutdown () {e.shutdown (); }
        Danh sách công khai shutdownNow () {return e.shutdownNow (); }
        công khai boolean isShutdown () {return e.isShutdown (); }
        công khai boolean isTermined () {return e.isTermined (); }
        boolean awaitTermination (thời gian chờ dài, đơn vị TimeUnit)
                ném Interrupttedception {
            trả lại e.awaitTermination (thời gian chờ, đơn vị);
        }
        công khai Tương lai (Nhiệm vụ có thể chạy) {
            trả lại e.submit (nhiệm vụ);
        }
        công khai Tương lai (Nhiệm vụ có thể gọi) {
            trả lại e.submit (nhiệm vụ);
        }
        công khai Tương lai (Nhiệm vụ có thể chạy, kết quả T) {
            trả lại e.submit (nhiệm vụ, kết quả);
        }
        Danh sách công khai> invoke ALL (Bộ sưu tập> tác vụ)
                ném Interrupttedception {
            trả lại e.invoke ALL (nhiệm vụ);
        }
        Danh sách công khai> invoke ALL (Bộ sưu tập> tác vụ,
                                             thời gian chờ lâu, đơn vị TimeUnit)
                ném Interrupttedception {
            trả lại e.invoke ALL (nhiệm vụ, thời gian chờ, đơn vị);
        }
        công khai T invokeAny (Bộ sưu tập> nhiệm vụ)
                ném Interrupttedception, ExecutException {
            trả lại e.invokeAny (nhiệm vụ);
        }
        công khai T invokeAny (Bộ sưu tập> nhiệm vụ,
                               thời gian chờ lâu, đơn vị TimeUnit)
                ném Interrupttedception, ExecutException, TimeoutException {
            trả lại e.invokeAny (nhiệm vụ, thời gian chờ, đơn vị);
        }
    }



    private SingleThreadExecutorWithExceptions () {}
}

Rất tiếc, việc sử dụng hoàn thiện là hơi không ổn định, vì nó sẽ chỉ được gọi là "sau này khi người thu gom rác thu thập nó" (hoặc có lẽ không phải trong trường hợp của một Chủ đề, dunno) ...
rogerdpack 28/03/18

1

Nếu bạn muốn giám sát quá trình thực thi tác vụ, bạn có thể quay 1 hoặc 2 luồng (có thể nhiều hơn tùy thuộc vào tải) và sử dụng chúng để nhận các tác vụ từ trình bao bọc ExecutCompletionService.


0

Nếu bạn ExecutorServiceđến từ một nguồn bên ngoài (nghĩa là không thể phân lớp ThreadPoolExecutorvà ghi đè afterExecute()), bạn có thể sử dụng proxy động để đạt được hành vi mong muốn:

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

Điều này là do AbstractExecutorService :: submitgói của bạn runnablevàoRunnableFuture (không có gì nhưng FutureTask) như dưới đây

AbstractExecutorService.java

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

Sau đó executesẽ chuyển nó đến WorkerWorker.run() sẽ gọi bên dưới.

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);
    }
}

Cuối cùng task.run();trong cuộc gọi mã trên sẽ gọi FutureTask.run(). Đây là mã xử lý ngoại lệ, vì điều này bạn KHÔNG nhận được ngoại lệ mong đợi.

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

Điều này tương tự như giải pháp của mmm, nhưng dễ hiểu hơn một chút. Có các nhiệm vụ của bạn mở rộng một lớp trừu tượng bao bọc phương thức 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

Thay vì phân lớp ThreadPoolExecutor, tôi sẽ cung cấp cho nó một phiên bản ThreadFactory để tạo Chủ đề mới và cung cấp cho họ một UncaughtExceptionHandler


3
Tôi cũng đã thử điều này, nhưng phương thức unsaughtException dường như không bao giờ được gọi. Tôi tin rằng điều này là do một luồng công nhân trong lớp ThreadPoolExecutor đang bắt các ngoại lệ.
Tom

5
Phương thức unsaughtException không được gọi vì phương thức gửi của ExecutorService đang gói Callable / Runnable trong tương lai; ngoại lệ đang bị bắt ở đó.
Emil Ngồi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.