जावा एक्ज़ीक्यूटर सर्विस से अपवादों को संभालना


213

मैं जावा का उपयोग करने की कोशिश कर रहा हूं ThreadPoolExecutor निश्चित संख्या में थ्रेड्स के साथ भारी वजन कार्यों को चलाने कक्षा । प्रत्येक कार्य में कई स्थान हैं जिनके दौरान अपवादों के कारण यह विफल हो सकता है।

मैंने उप-वर्गित किया है 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.isDone () [कृपया असली एपीआई नाम पढ़ें] सत्य वापस आ जाएगा, तब भी जब रननेबल ग़लती से समाप्त हो गया हो। क्योंकि कार्य वास्तविक के लिए किया जाता है।
जय पंडित

जवाबों:


156

से डॉक्स :

नोट: जब कार्यों को कार्यों में संलग्न किया जाता है (जैसे कि FutureTask) या तो स्पष्ट रूप से या सबमिट करने के तरीकों के माध्यम से, ये कार्य ऑब्जेक्ट कम्प्यूटेशनल अपवादों को पकड़ते हैं और बनाए रखते हैं, और इसलिए वे अचानक समाप्ति का कारण नहीं बनते हैं, और आंतरिक अपवाद इस पद्धति से पारित नहीं होते हैं ।

जब आप एक Runnable सबमिट करते हैं, तो यह Future में लिपट जाएगा।

आपका 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
धन्यवाद, मैं इस समाधान का उपयोग कर समाप्त हुआ। इसके अतिरिक्त, अगर किसी को दिलचस्पी है: दूसरों ने एक्सक्यूसोर सर्विस को उप-वर्ग नहीं करने का सुझाव दिया है, लेकिन मैंने वैसे भी किया क्योंकि मैं कार्यों की निगरानी करना चाहता था क्योंकि वे सभी को समाप्त करने के लिए इंतजार करने के बजाय पूरा करते हैं और फिर लौटाए गए सभी पर कॉल () प्राप्त करते हैं। ।
टॉम

1
निष्पादक को उपवर्ग करने के लिए एक और तरीका FutureTask को उपवर्गित करना है और इसकी 'की गई' विधि को ओवरराइड करना है
nos

1
टॉम >> क्या आप कृपया अपना नमूना स्निपेट कोड पोस्ट कर सकते हैं, जहाँ आपने कार्यों को पूरा करने के लिए एक्जिक्यूटर
सर्विस को उप

1
यदि आप ComplableFuture.runAsync का उपयोग कर रहे हैं तो यह उत्तर काम नहीं करेगा क्योंकि afterExecute में एक ऐसी वस्तु होगी जो पैकेज निजी है और फेंकने योग्य तक पहुंचने के लिए कोई रास्ता नहीं है। मैंने कॉल को लपेट कर इसके चारों ओर मिला। नीचे मेरा जवाब देखें।
mmm

2
अगर भविष्य का उपयोग कर पूरा किया जाता है तो क्या हमें जांचना होगा future.isDone()? चूंकि पूरा afterExecuteहोने के बाद चलाया Runnableजाता है, मुझे लगता है कि future.isDone()हमेशा रिटर्न मिलता है true
Searene

248

चेतावनी : यह ध्यान दिया जाना चाहिए कि यह समाधान कॉलिंग थ्रेड को अवरुद्ध करेगा।


यदि आप कार्य द्वारा फेंके गए अपवादों को संसाधित करना चाहते हैं, तो आमतौर पर इसके Callableबजाय उपयोग करना बेहतर होता है Runnable

Callable.call() जाँच किए गए अपवादों को फेंकने की अनुमति है, और ये कॉलिंग थ्रेड को वापस प्रचारित करते हैं:

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

यदि Callable.call()एक अपवाद फेंकता है, तो यह एक लपेटा जाएगा ExecutionExceptionऔर इसके द्वारा फेंका जाएगा Future.get()

यह उपवर्ग के लिए बहुत बेहतर होने की संभावना है ThreadPoolExecutor। यह आपको अपवाद को पुनर्प्राप्त करने वाले कार्य को फिर से प्रस्तुत करने का अवसर भी देता है।


5
> Callable.call () को चेक किए गए अपवादों को फेंकने की अनुमति है, और ये कॉलिंग थ्रेड पर वापस प्रचारित हो जाते हैं: ध्यान दें कि फेक अपवाद कॉलिंग थ्रेड को केवल तभी प्रचारित करेगा जब future.get()या उसके अतिभारित संस्करण को कहा जाता है।
nhylated

16
यह सही है, लेकिन क्या होगा यदि मैं समानांतर में कार्यों को चलाता हूं और निष्पादन को अवरुद्ध नहीं करना चाहता हूं?
ग्रिगोरी किसलिन

43
इस समाधान का उपयोग न करें, क्योंकि यह ExecutorService का उपयोग करने के पूरे उद्देश्य को तोड़ता है। एग्ज़कॉर्स सर्विस एक अतुल्यकालिक निष्पादन तंत्र है जो पृष्ठभूमि में कार्यों को निष्पादित करने में सक्षम है। यदि आप future.get कहते हैं () सही निष्पादित होने के बाद यह कॉलिंग थ्रेड को ब्लॉक करेगा जब तक कि कार्य समाप्त नहीं हो जाता।
user1801374

2
यह समाधान इतना उच्च रेटेड नहीं होना चाहिए। Future.get () तुल्यकालिक रूप से काम करता है और जब तक Runnable या Callable को निष्पादित नहीं किया जाता है, तब तक एक अवरोधक के रूप में कार्य करेगा और जैसा कि ऊपर कहा गया है, सेवा प्रदाता का उपयोग करने के उद्देश्य को पराजित करता है
Super Hans

2
जैसा कि #nhylated ने बताया, यह एक jdk BUG के योग्य है। अगर Future.get () नहीं कहा जाता है, तो Callable के किसी भी अनकैप्ड अपवाद को चुपचाप अनदेखा कर दिया जाता है। बहुत खराब डिज़ाइन .... एक पुस्तकालय का उपयोग करने के लिए सिर्फ 1+ दिन बिताए और इसका उपयोग किया और चुपचाप अपवादों को अनदेखा किया। और, यह अभी भी jdk12 में मौजूद है।
बेन जियांग

18

इस व्यवहार के लिए स्पष्टीकरण javadoc में afterExecute के लिए सही है :

नोट: जब कार्यों को कार्यों में संलग्न किया जाता है (जैसे कि FutureTask) या तो स्पष्ट रूप से या सबमिट करने के तरीकों के माध्यम से, ये कार्य ऑब्जेक्ट कम्प्यूटेशनल अपवादों को पकड़ते हैं और बनाए रखते हैं, और इसलिए वे अचानक समाप्ति का कारण नहीं बनते हैं, और आंतरिक अपवाद इस पद्धति से पारित नहीं होते हैं ।


10

निष्पादक को सौंपे गए आपूर्ति-योग्य रनवे को लपेटकर मैं इसके चारों ओर हो गया।

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

3
की whenComplete()विधि का उपयोग करके आप पठनीयता में सुधार कर सकते हैं CompletableFuture
एडुआर्ड विर्च

@EduardWirch यह काम करता है, लेकिन आप कब ()
अक्षत

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

एक और समाधान ManagedTask और ManagedTaskListener का उपयोग करना होगा

आप एक की जरूरत है प्रतिदेय या Runnable जो औजार इंटरफ़ेस ManagedTask

विधि getManagedTaskListenerआपको इच्छित उदाहरण देता है।

public ManagedTaskListener getManagedTaskListener() {

और आप ManagedTaskListenertaskDone विधि में लागू करते हैं :

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

प्रबंधित कार्य जीवनचक्र और श्रोता के बारे में अधिक जानकारी ।


2

यह काम

  • यह SingleThreadExecutor से लिया गया है, लेकिन आप इसे आसानी से अनुकूलित कर सकते हैं
  • जावा 8 लामदास कोड, लेकिन ठीक करना आसान है

यह एक एकल थ्रेड के साथ एक एक्सेक्यूटर बनाएगा, जिसमें बहुत सारे कार्य मिल सकते हैं; और अगले एक के साथ शुरू होने के लिए निष्पादन को समाप्त करने के लिए इंतजार करेंगे

Uncaugth त्रुटि या अपवाद के मामले में uncaughtExceptionHandler इसके शिकार हो सकते

सार्वजनिक अंतिम वर्ग SingleThreadExecutorWithException {

    सार्वजनिक स्थैतिक निष्पादक सेवा NewSingleThreadExecutorWithException (अंतिम थ्रेड ।UncaughtExceptionHandler uncaughtExceptionHandler) {

        थ्रेड फैक्ट्री फैक्ट्री = (रनने योग्य रननेबल) -> {
            अंतिम थ्रेड newThread = नया थ्रेड (runnable, "SingleThreadExecutorWithException");
            newThread.setUncaughtExceptionHandler ((अंतिम थ्रेड caugthTread, अंतिम फेंकने योग्य) -> {
                uncaughtExceptionHandler.uncaughtException (caugthThread, फेंकने योग्य);
            });
            वापसी नईट्रेड;
        };
        नया फ़ाइनल करने योग्य
                (नया थ्रेडपूल एक्ज़ीक्यूटर (1, 1,)
                        0 एल, टाइम यूनीट.मिलीसैंस,
                        नया लिंक्डब्लॉकिंग क्यू (),
                        कारखाना) {


                    संरक्षित शून्य afterExecute (Runnable runnable, Throwable फेंकने योग्य) {
                        super.afterExecute (runnable, throwable);
                        अगर (फेंकने योग्य == अशक्त और अस्थिर) भविष्य) {
                            प्रयत्न {
                                भावी भविष्य = (भविष्य में) चल सकने योग्य;
                                अगर (Future.isDone ()) {
                                    future.get ();
                                }
                            } कैच (कैंसेलेशनएक्स अपवाद) {
                                फेंकने योग्य = CE;
                            } कैच (एक्सेप्शन एक्ससेप्शन ee) {
                                फेंकने योग्य = ee.getCause ();
                            } पकड़ (बाधित)
                                Thread.currentThread () बाधा ()।; // अनदेखा / रीसेट
                            }
                        }
                        अगर (फेंकने योग्य! = अशक्त) {
                            uncaughtExceptionHandler.uncaughtException (Thread.currentThread (), फेंकने योग्य);
                        }
                    }
                });
    }



    निजी स्थिर वर्ग FinalizableDelegatedExecutorService
            विस्तारित प्रतिनिधि अभियोजक सेवा {
        FinalizableDelegatedExecutorService (ExecutorService निष्पादक) {
            सुपर (निष्पादक);
        }
        संरक्षित शून्य अंतिम () {
            super.shutdown ();
        }
    }

    / **
     * एक रैपर क्लास जो केवल एक्सेकॉर्स सर्विस तरीकों को उजागर करता है
     * एक निष्पादन सेवा के कार्यान्वयन।
     * /
    निजी स्थिर वर्ग DelegatedExecutorService का विस्तार करता है AbstractExecutorService {
        निजी अंतिम ExecutorService ई;
        DelegatedExecutorService (ExecutorService निष्पादक) {e = निष्पादक; }
        public void execute (Runnable कमांड) {e.execute (कमांड); }
        सार्वजनिक शून्य बंद () {e.shutdown (); }
        सार्वजनिक सूची shutdownNow () {रिटर्न e.shutdownNow (); }
        सार्वजनिक बूलियन हैशटडाउन () {वापसी e.isSutdown (); }
        सार्वजनिक बूलियन को पृथक () {वापसी e.isTerminated (); }
        सार्वजनिक बूलियन प्रतीक्षारत (लंबे समय तक, टाइम यूनिट इकाई)
                थ्रो बाधित
            वापसी ई.टाइटर्मिनेशन (टाइमआउट, यूनिट);
        }
        सार्वजनिक भविष्य का सबमिट (रन करने योग्य कार्य) {
            वापसी e.submit (कार्य);
        }
        सार्वजनिक भविष्य का सबमिट (कॉल करने योग्य कार्य) {
            वापसी e.submit (कार्य);
        }
        सार्वजनिक भविष्य का सबमिट (रन करने योग्य कार्य, T परिणाम) {
            वापसी e.submit (कार्य, परिणाम);
        }
        सार्वजनिक सूची> invokeAll (संग्रह> कार्य)
                थ्रो बाधित
            वापसी e.invokeAll (कार्य);
        }
        सार्वजनिक सूची> invokeAll (संग्रह> कार्य,
                                             लंबी समय सीमा, टाइम यूनिट इकाई)
                थ्रो बाधित
            वापसी e.invokeAll (कार्य, टाइमआउट, यूनिट);
        }
        सार्वजनिक T invokeAny (संग्रह> कार्य)
                बाधा डालती है, अपवाद, अपवाद
            वापसी e.invokeAny (कार्य);
        }
        public T invokeAny (संग्रह> कार्य,
                               लंबी समय सीमा, टाइम यूनिट इकाई)
                थ्रो इंटरप्टेड अपवाद, एक्सेप्शन एक्ससेप्शन, टाइमआउट एक्ससेप्शन {
            वापसी e.invokeAny (कार्य, टाइमआउट, यूनिट);
        }
    }



    निजी एकलट्रेड एक्सिक्यूटरWithException () {}
}

अंतिम रूप से उपयोग करना थोड़ा दुर्भाग्यपूर्ण है, क्योंकि इसे केवल "बाद में" कहा जाएगा जब कचरा संग्रहकर्ता इसे एकत्र करता है "(या शायद थ्रेड,
डननो

1

यदि आप कार्य के निष्पादन की निगरानी करना चाहते हैं, तो आप 1 या 2 थ्रेड्स (संभवत: लोड के आधार पर अधिक) स्पिन कर सकते हैं और एक्सेकॉक्शनकंप्लीशन सेपर रैपर से कार्यों को लेने के लिए उनका उपयोग कर सकते हैं।


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अपने (कुछ भी नहीं ) runnableमें लपेट रहा हैRunnableFutureFutureTask

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इसे पास करेंगे Workerऔर Worker.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

यह एमएमएम के समाधान के समान है, लेकिन थोड़ा अधिक समझने योग्य है। अपने कार्यों में एक अमूर्त वर्ग का विस्तार करें जो रन () पद्धति को लपेटता है।

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

थ्रेडपूल एक्सक्लूसर को उप-वर्गित करने के बजाय, मैं इसे थ्रेडफैक्ट्री उदाहरण के साथ प्रदान करूंगा जो नए थ्रेड बनाता है और उन्हें एक अनक्रेडिसेप्शनहैंडलर प्रदान करता है।


3
मैंने यह भी कोशिश की, लेकिन बिना किसी अपवाद के विधि कभी नहीं कहा जाता है। मेरा मानना ​​है कि ऐसा इसलिए है क्योंकि ThreadPoolExecutor वर्ग में एक कार्यकर्ता थ्रेड अपवादों को पकड़ रहा है।
टॉम

5
बिना किसी अपवाद के विधि को नहीं कहा जाता है क्योंकि ExecutorService की सबमिट विधि भविष्य में Callable / Runnable को लपेट रही है; अपवाद वहाँ कब्जा किया जा रहा है।
एमिल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.