it-swarm.com.de

ScheduledExecutorService Ausnahmebehandlung

Ich benutze ScheduledExecutorService, um regelmäßig eine Methode auszuführen.

p-Code:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> handle =
        scheduler.scheduleWithFixedDelay(new Runnable() {
             public void run() { 
                 //Do business logic, may Exception occurs
             }
        }, 1, 10, TimeUnit.SECONDS);

Meine Frage:

Wie kann der Scheduler fortgesetzt werden, wenn run() eine Ausnahme auslöst? Soll ich alle Ausnahmen in der Methode run() ausfangen? Oder eine integrierte Rückmeldemethode, um die Ausnahme zu behandeln? Vielen Dank!

Sie sollten das ScheduledFuture-Objekt verwenden, das von Ihrer scheduler.scheduleWithFixedDelay(...) zurückgegeben wird, wie folgt:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> handle =
        scheduler.scheduleWithFixedDelay(new Runnable() {
             public void run() { 
                 throw new RuntimeException("foo");
             }
        }, 1, 10, TimeUnit.SECONDS);

// Create and Start an exception handler thread
// pass the "handle" object to the thread
// Inside the handler thread do :
....
try {
  handle.get();
} catch (ExecutionException e) {
  Exception rootException = e.getCause();
}
32
arun_suresh

tl; dr

Jede Ausnahme, die Ihre run-Methode außer Kraft setzt, stoppt die weitere Arbeit ohne vorherige Ankündigung.

Verwenden Sie immer einen try-catch in Ihrer run-Methode. Versuchen Sie, sich zu erholen, wenn die geplante Aktivität fortgesetzt werden soll.

@Override
public void run ()
{
    try {
        doChore();
    } catch ( Exception e ) { 
        logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
    }
}

Das Problem

Die Frage bezieht sich auf den kritischen Trick mit einem ScheduledExecutorService : Jede geworfene Ausnahme oder ein Fehler, der den Executor erreicht, bewirkt, dass der Executor angehalten wird. Keine Aufrufe mehr für das Runable, keine Arbeit mehr. Diese Arbeitsunterbrechung geschieht stumm, Sie werden nicht informiert. Dieses naughty-sprachige Blog-Posting erzählt auf unterhaltsame Weise, wie es schwierig ist, etwas über dieses Verhalten zu erfahren.

Die Lösung

Das answer by yegor256 und das answer by arun_suresh scheinen beide grundsätzlich korrekt zu sein. Zwei Probleme mit diesen Antworten: 

  • Fangfehler sowie Ausnahmen
  • Etwas kompliziert

Fehler und Ausnahmen?

In Java fangen wir normalerweise nur Ausnahmen , nicht Fehler . In diesem speziellen Fall von ScheduledExecutorService bedeutet das Versäumnis, beide zu fangen, einen Arbeitsausfall. Vielleicht möchten Sie beide fangen. Ich bin mir nicht zu 100% sicher, dass ich die Auswirkungen des Erfassens aller Fehler nicht vollständig kenne. Bitte korrigieren Sie mich wenn nötig.

Eine Möglichkeit, Ausnahmen und Fehler abzufangen, besteht darin, ihre Oberklasse Throwable abzufangen.

} catch ( Throwable t ) {

…eher, als…

} catch ( Exception e ) {

Einfachster Ansatz: Fügen Sie einfach einen Try-Catch hinzu.

Aber beide Antworten sind etwas kompliziert. Nur fürs Protokoll zeige ich die einfachste Lösung: 

Packen Sie den Code Ihres Runnables immer in einen Try-Catch, um alle Ausnahmen und abzufangen. 

Lambda-Syntax

Mit einem Lambda (ab Java 8).

final Runnable someChoreRunnable = () -> {
    try {
        doChore();
    } catch ( Throwable t ) {  // Catch Throwable rather than Exception (a subclass).
        logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
    }
};

Altmodische Syntax

Die altmodische Art, bevor Lambdas.

final Runnable someChoreRunnable = new Runnable()
{
    @Override
    public void run ()
    {
        try {
            doChore();
        } catch ( Throwable t ) {  // Catch Throwable rather than Exception (a subclass).
            logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
        }
    }
};

In jedem Run/Callable

Unabhängig von einem ScheduledExecutorService scheint es mir sinnvoll, immer eine allgemeine try-catch( Exception† e ) in any _ ​​ run Methode eines Runnable zu verwenden. Gleiches gilt für jede call Methode einer Callable .


Vollständiger Beispielcode

In der realen Arbeit würde ich wahrscheinlich Runnable eher als verschachtelt definieren. Dies ist jedoch ein ordentliches All-in-One-Beispiel.

package com.basilbourque.example;

import Java.time.ZoneId;
import Java.time.ZonedDateTime;
import Java.util.concurrent.Executors;
import Java.util.concurrent.ScheduledExecutorService;
import Java.util.concurrent.ScheduledFuture;
import Java.util.concurrent.TimeUnit;

/**
 *  Demo `ScheduledExecutorService`
 */
public class App {
    public static void main ( String[] args ) {
        App app = new App();
        app.doIt();
    }

    private void doIt () {

        // Demonstrate a working scheduled executor service.
        // Run, and watch the console for 20 seconds.
        System.out.println( "BASIL - Start." );

        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        ScheduledFuture < ? > handle =
                scheduler.scheduleWithFixedDelay( new Runnable() {
                    public void run () {
                        try {
                            // doChore ;   // Do business logic.
                            System.out.println( "Now: " + ZonedDateTime.now( ZoneId.systemDefault() ) );  // Report current moment.
                        } catch ( Exception e ) {
                            // … handle exception/error. Trap any unexpected exception here rather to stop it reaching and shutting-down the scheduled executor service.
                            // logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + e.getStackTrace() );
                        }   // End of try-catch.
                    }   // End of `run` method.
                } , 0 , 2 , TimeUnit.SECONDS );


        // Wait a long moment, for background thread to do some work.
        try {
            Thread.sleep( TimeUnit.SECONDS.toMillis( 20 ) );
        } catch ( InterruptedException e ) {
            e.printStackTrace();
        }

        // Time is up. Kill the executor service and its thread pool.
        scheduler.shutdown();

        System.out.println( "BASIL - Done." );

    }
}

Wann laufen.

BASIL - Start.

Jetzt: 2018-04-10T16: 46: 01.423286-07: 00 [America/Los_Angeles]

Jetzt: 2018-04-10T16: 46: 03.449178-07: 00 [America/Los_Angeles]

Jetzt: 2018-04-10T16: 46: 05.450107-07: 00 [America/Los_Angeles]

Jetzt: 2018-04-10T16: 46: 07.450586-07: 00 [America/Los_Angeles]

Jetzt: 2018-04-10T16: 46: 09.456076-07: 00 [America/Los_Angeles]

Jetzt: 2018-04-10T16: 46: 11.456872-07: 00 [America/Los_Angeles]

Jetzt: 2018-04-10T16: 46: 13.461944-07: 00 [America/Los_Angeles]

Jetzt: 2018-04-10T16: 46: 15.463837-07: 00 [America/Los_Angeles]

Jetzt: 2018-04-10T16: 46: 17.469218-07: 00 [America/Los_Angeles]

Jetzt: 2018-04-10T16: 46: 19.473935-07: 00 [America/Los_Angeles]

BASILIK - Fertig.


† Oder vielleicht Throwable anstelle von Exception , um auch Error -Objekte abzufangen.

76
Basil Bourque

Ich weiß, dass dies eine alte Frage ist, aber wenn jemand verzögert CompletableFuture mit ScheduledExecutorService verwendet, sollte dies folgendermaßen behandelt werden:

private static CompletableFuture<String> delayed(Duration delay) {
    CompletableFuture<String> delayed = new CompletableFuture<>();
    executor.schedule(() -> {
        String value = null;
        try {
            value = mayThrowExceptionOrValue();
        } catch (Throwable ex) {
            delayed.completeExceptionally(ex);
        }
        if (!delayed.isCompletedExceptionally()) {
            delayed.complete(value);
        }
    }, delay.toMillis(), TimeUnit.MILLISECONDS);
    return delayed;
}

ausnahme in CompletableFuture und behandeln:

CompletableFuture<String> delayed = delayed(Duration.ofSeconds(5));
delayed.exceptionally(ex -> {
    //handle exception
    return null;
}).thenAccept(value -> {
    //handle value
});
3
MBec

Eine andere Lösung wäre, eine Ausnahme in der Variablen Runnable zu schlucken. Sie können eine praktische VerboseRunnable class aus jcabi-log verwenden, zum Beispiel:

import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
  new VerboseRunnable(
    Runnable() {
      public void run() { 
        // do business logic, may Exception occurs
      }
    },
    true // it means that all exceptions will be swallowed and logged
  ),
  1, 10, TimeUnit.SECONDS
);
3
yegor256

Inspiriert von der @MBec-Lösung schrieb ich einen generischen Nice-Wrapper für den ScheduledExecutorService, der:

  • fängt und druckt alle nicht behandelten Ausnahmen.
  • gibt eine Java 8 CompletableFuture anstelle einer Zukunft zurück.

:)

import Java.util.List;
import Java.util.concurrent.Callable;
import Java.util.concurrent.CompletableFuture;
import Java.util.concurrent.ScheduledExecutorService;
import Java.util.concurrent.TimeUnit;

/**
 * This class use as a wrapper for the Native Java ScheduledExecutorService class.
 * It was created in order to address the very unpleasant scenario of silent death!
 * explanation: each time an unhandled exception get thrown from a running task that runs by ScheduledExecutorService
 * the thread will die and the exception will die with it (nothing will propagate back to the main thread).
 *
 * However, HonestScheduledExecutorService will gracefully print the thrown exception with a custom/default message,
 * and will also return a Java 8 compliant CompletableFuture for your convenience :)
 */
@Slf4j
public class HonestScheduledExecutorService {

    private final ScheduledExecutorService scheduledExecutorService;
    private static final String DEFAULT_FAILURE_MSG = "Failure occurred when running scheduled task.";

    HonestScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
        this.scheduledExecutorService = scheduledExecutorService;
    }

    public CompletableFuture<Object> scheduleWithFixedDelay(Callable callable, String onFailureMsg, long initialDelay, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(onFailureMsg) ? DEFAULT_FAILURE_MSG : onFailureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, delay, unit);

        return delayed;
    }

    public CompletableFuture<Void> scheduleWithFixedDelay(Runnable runnable, String onFailureMsg, long initialDelay, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(onFailureMsg) ? DEFAULT_FAILURE_MSG : onFailureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, delay, unit);

        return delayed;
    }

    public CompletableFuture<Object> schedule(Callable callable, String failureMsg, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.schedule(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, delay, unit);

        return delayed;
    }

    public CompletableFuture<Void> schedule(Runnable runnable, String failureMsg, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.schedule(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, delay, unit);

        return delayed;
    }

    public CompletableFuture<Object> scheduleAtFixedRate(Callable callable, String failureMsg, long initialDelay, long period, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, period, unit);

        return delayed;
    }

    public CompletableFuture<Void> scheduleAtFixedRate(Runnable runnable, String failureMsg, long initialDelay, long period, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, period, unit);

        return delayed;
    }

    public CompletableFuture<Object> execute(Callable callable, String failureMsg) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.execute(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        });

        return delayed;
    }

    public CompletableFuture<Void> execute(Runnable runnable, String failureMsg) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.execute(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        });

        return delayed;
    }

    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        return scheduledExecutorService.awaitTermination(timeout, unit);
    }

    public List<Runnable> shutdownNow() {
        return scheduledExecutorService.shutdownNow();
    }

    public void shutdown() {
        scheduledExecutorService.shutdown();
    }

}
2
dorony

Alte Frage, aber die akzeptierte Antwort gibt keine Erklärungen und liefert ein schlechtes Beispiel. Die am besten bewertete Antwort stimmt in einigen Punkten, fordert Sie jedoch auf, catch Ausnahmen in jede Runnable.run() -Methode einzufügen.
Ich stimme nicht zu, weil:

  • es ist nicht ordentlich: nicht standard für eine aufgabe, ihre eigenen ausnahmen zu erfassen.
  • es ist nicht robust: Eine neue Runnable-Unterklasse kann vergessen, den Ausnahmefang und das damit verbundene Failover auszuführen.
  • es besiegt die niedrige Kopplung, die durch Aufgaben gefördert wird, da dies die auszuführenden Aufgaben mit der Art der Behandlung des Aufgabenergebnisses koppelt.
  • es werden Verantwortlichkeiten gemischt: Dies ist nicht die Aufgabe, die Ausnahme zu behandeln oder die Ausnahme dem Aufrufer mitzuteilen. Eine Aufgabe muss ausgeführt werden.

Ich denke, dass die Weitergabe von Ausnahmen durch das ExecutorService -Framework erfolgen sollte, und tatsächlich bietet es diese Funktion.
Außerdem ist es keine gute Idee, zu clever zu sein, indem man versucht, die ExecutorService - Arbeitsweise kurzzuschließen: Das Framework kann sich weiterentwickeln, und Sie möchten es auf standardmäßige Weise verwenden.
Wenn Sie das ExecutorService -Framework endlich seinen Job machen lassen, bedeutet dies nicht, dass Sie die nachfolgende Aufruftask anhalten müssen.
Wenn eine geplante Aufgabe auf ein Problem stößt, liegt dies in der Verantwortung des Anrufers, die Aufgabe entsprechend der Ursache des Problems neu zu planen oder nicht.
Jede Schicht hat ihre Verantwortlichkeiten. Halten Sie diesen Code klar und wartbar.


ScheduledFuture.get (): Die richtige API zum Abfangen von Ausnahmen und Fehlern ist in der Task aufgetreten

ScheduledExecutorService.scheduleWithFixedDelay()/scheduleAtFixRate() in ihrer Spezifikation angeben:

Wenn bei einer Ausführung der Aufgabe eine Ausnahme auftritt, werden nachfolgende Ausführungen unterdrückt. Andernfalls wird die Aufgabe nur durch Abbruch oder Kündigung des Executors beendet.

Dies bedeutet, dass ScheduledFuture.get() nicht bei jedem geplanten Aufruf zurückgegeben wird, sondern bei dem letzten Aufruf der Aufgabe. Dies ist ein Abbruch der Aufgabe: verursacht durch ScheduledFuture.cancel() oder eine eingeworfene Ausnahme die Aufgabe.
Die Behandlung der ScheduledFuture -Rückgabe, um die Ausnahme mit ScheduledFuture.get() zu erfassen, sieht also richtig aus:

  try {
    future.get();

  } catch (InterruptedException e) {
    // ... to handle
  } catch (ExecutionException e) {
    // ... and unwrap the exception OR the error that caused the issue
    Throwable cause = e.getCause();       
  }

Beispiel mit dem Standardverhalten: Anhalten der Planung, wenn bei der Ausführung einer Aufgabe ein Problem auftritt

Es führt eine Task aus, die für die dritte Ausführung eine Ausnahme ausgelöst hat, und beendet die Zeitplanung. In einigen Szenarien möchten wir das.

import Java.util.concurrent.ExecutionException;
import Java.util.concurrent.Executors;
import Java.util.concurrent.Future;
import Java.util.concurrent.ScheduledExecutorService;
import Java.util.concurrent.TimeUnit;
import Java.util.concurrent.atomic.AtomicInteger;

public class ScheduledExecutorServiceWithException {

  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

    // variable used to thrown an error at the 3rd task invocation
    AtomicInteger countBeforeError = new AtomicInteger(3);

    // boolean allowing to leave the client to halt the scheduling task or not after a failure
    Future<?> futureA = executor
        .scheduleWithFixedDelay(new MyRunnable(countBeforeError), 1, 2, TimeUnit.SECONDS);
    try {
      System.out.println("before get()");
      futureA.get(); // will return only if canceled
      System.out.println("after get()");
    } catch (InterruptedException e) {
      // handle that : halt or no
    } catch (ExecutionException e) {
      System.out.println("exception caught :" + e.getCause());
    }

    // shutdown the executorservice
    executor.shutdown();
  }

  private static class MyRunnable implements Runnable {

    private final AtomicInteger invocationDone;

    public MyRunnable(AtomicInteger invocationDone) {
      this.invocationDone = invocationDone;
    }

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ", execution");
      if (invocationDone.decrementAndGet() == 0) {
        throw new IllegalArgumentException("ohhh an Exception in MyRunnable");
      }
    }
  }
}

Ausgabe :

 vor get () 
 Pool-1-Thread-1, Ausführung 
 Pool-1-Thread-1, Ausführung 
 Pool-1-Thread-1, Exception 
 abgefangen: Java.lang.IllegalArgumentException: ohhh eine Exception in MyRunnable 

Beispiel mit der Möglichkeit, die Planung fortzusetzen, wenn bei der Ausführung einer Aufgabe ein Problem auftritt

Es führt eine Task aus, die bei den ersten beiden Ausführungen eine Ausnahme und bei der dritten einen Fehler auslöst. Wir können sehen, dass der Client der Tasks entscheiden kann, ob die Planung angehalten werden soll oder nicht: hier gehe ich in Ausnahmefällen vor und stoppe im Fehlerfall.

import Java.util.concurrent.ExecutionException;
import Java.util.concurrent.Executors;
import Java.util.concurrent.Future;
import Java.util.concurrent.ScheduledExecutorService;
import Java.util.concurrent.TimeUnit;
import Java.util.concurrent.atomic.AtomicInteger;

public class ScheduledExecutorServiceWithException {

  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

    // variable used to thrown an error at the 3rd task invocation
    AtomicInteger countBeforeError = new AtomicInteger(3);

    // boolean allowing to leave the client to halt the scheduling task or not after a failure
    boolean mustHalt = true;
    do {
      Future<?> futureA = executor
              .scheduleWithFixedDelay(new MyRunnable(countBeforeError), 1, 2, TimeUnit.SECONDS);
      try {
        futureA.get(); // will return only if canceled
      } catch (InterruptedException e) {
        // handle that : halt or not halt
      } catch (ExecutionException e) {
        if (e.getCause() instanceof Error) {
          System.out.println("I halt in case of Error");
          mustHalt = true;
        } else {
          System.out.println("I reschedule in case of Exception");
          mustHalt = false;
        }
      }
    }
    while (!mustHalt);
    // shutdown the executorservice
    executor.shutdown();
  }

  private static class MyRunnable implements Runnable {

    private final AtomicInteger invocationDone;

    public MyRunnable(AtomicInteger invocationDone) {
      this.invocationDone = invocationDone;
    }

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ", execution");

      if (invocationDone.decrementAndGet() == 0) {
        throw new Error("ohhh an Error in MyRunnable");
      } else {
        throw new IllegalArgumentException("ohhh an Exception in MyRunnable");
      }
    }
  }
}

Ausgabe :

 Pool-1-Thread-1, Ausführung 
 Ich plane im Falle einer Ausnahme neu 
 Pool-1-Thread-1, Ausführung 
 Ich plane im Falle einer Ausnahme neu 
 Pool-1-Thread-2, Ausführung 
 Ich halte im Fehlerfall an 
1
davidxxx

Jede Ausnahme im run () eines Threads, der an (ScheduledExecutorService) übergeben wird, wird nie ausgegeben. Wenn Sie future.get () verwenden, um den Status zu erhalten, wartet der Haupt-Thread endlos 

0
Guru