2012-12-18 6 views
9

Ho avuto difficoltà con il problema di usabilità di SwingWorker che mangiava qualsiasi eccezione generata nell'attività in background, ad esempio, descritta on this SO thread. Quel thread fornisce una buona descrizione del problema, ma non discute il recupero dell'eccezione originale.Eccezioni di SwingWorker perse anche quando si utilizzano le classi wrapper

L'applet che ho ricevuto deve propagare l'eccezione verso l'alto. Ma non sono stato in grado nemmeno di prenderlo. Sto utilizzando la classe di wrapper SimpleSwingWorker da this blog entry specificatamente per provare a risolvere questo problema. È una classe abbastanza piccola ma la ripropongo qui alla fine solo per riferimento.

Il codice chiamante appare ampiamente come

try { 
    // lots of code here to prepare data, finishing with 
    SpecialDataHelper helper = new SpecialDataHelper(...stuff...); 
    helper.execute(); // this will call get+done on the actual worker 
} catch (Throwable e) { 
    // used "Throwable" here in desperation to try and get 
    // anything at all to match, including unchecked exceptions 
    // 
    // no luck, this code is never ever used :-(
} 

Gli involucri:

class SpecialDataHelper extends SimpleSwingWorker { 
    public SpecialDataHelper (SpecialData sd) { 
     this.stuff = etc etc etc; 
    } 
    public Void doInBackground() throws Exception { 
     OurCodeThatThrowsACheckedException(this.stuff); 
     return null; 
    } 
    protected void done() { 
     // called only when successful 
     // never reached if there's an error 
    } 
} 

La caratteristica di SimpleSwingWorker è che done()/get() metodi del SwingWorker reale sono chiamati automaticamente. Questo, in teoria, ripaga tutte le eccezioni avvenute in background. In pratica, nulla viene mai catturato e non so nemmeno perché.

La classe SimpleSwingWorker, per riferimento, e con niente eliso per brevità:

import java.util.concurrent.ExecutionException; 
import javax.swing.SwingWorker; 

/** 
* A drop-in replacement for SwingWorker<Void,Void> but will not silently 
* swallow exceptions during background execution. 
* 
* Taken from http://jonathangiles.net/blog/?p=341 with thanks. 
*/ 
public abstract class SimpleSwingWorker { 
    private final SwingWorker<Void,Void> worker = 
     new SwingWorker<Void,Void>() { 
      @Override 
      protected Void doInBackground() throws Exception { 
       SimpleSwingWorker.this.doInBackground(); 
       return null; 
      } 

      @Override 
      protected void done() { 
       // Exceptions are lost unless get() is called on the 
       // originating thread. We do so here. 
       try { 
        get(); 
       } catch (final InterruptedException ex) { 
        throw new RuntimeException(ex); 
       } catch (final ExecutionException ex) { 
        throw new RuntimeException(ex.getCause()); 
       } 
       SimpleSwingWorker.this.done(); 
      } 
    }; 

    public SimpleSwingWorker() {} 

    protected abstract Void doInBackground() throws Exception; 
    protected abstract void done(); 

    public void execute() { 
     worker.execute(); 
    } 
} 
+3

Tutto questo si basa su una falsa ipotesi. Leggi il [javadoc per il metodo get()] (http://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html#get%28%29): genera una ExecutionException se il calcolo dello sfondo ha generato un'eccezione. –

+0

Vedere anche questo [Q & A] (http://stackoverflow.com/q/7053865/230513). – trashgod

+0

@ JBNizet Sì, è per questo che le chiamate done() di SimpleSwingWorker get(), intercettano ExecutionException e la riflettono come nuova RuntimeException. Non è questo il punto? Se no, allora stiamo parlando l'un l'altro e dovrai essere più esplicito. –

risposta

20

Dimentica il tuo wrapper, che mangia le eccezioni, mentre lo SwingWorker no. Ecco come dovrebbe essere utilizzato uno SwingWorker, e come si dovrebbe gestire un'eccezione specifica lanciata dal compito di fondo:

class MeaningOfLifeFinder extends SwingWorker<String, Object> { 
    @Override 
    public String doInBackground() throws SomeException { 
     return findTheMeaningOfLife(); 
    } 

    @Override 
    protected void done() { // called in the EDT. You can update the GUI here, show error dialogs, etc. 
     try { 
      String meaningOfLife = get(); // this line can throw InterruptedException or ExecutionException 
      label.setText(meaningOfLife); 
     } 
     catch (ExecutionException e) { 
      Throwable cause = e.getCause(); // if SomeException was thrown by the background task, it's wrapped into the ExecutionException 
      if (cause instanceof SomeException) { 
       // TODO handle SomeException as you want to 
      } 
      else { // the wrapped throwable is a runtime exception or an error 
       // TODO handle any other exception as you want to 
      } 
     } 
     catch (InterruptedException ie) { 
      // TODO handle the case where the background task was interrupted as you want to 
     } 
    } 
} 
+0

Immagino che questo sia il motivo per cui mi sento frustrato. 'SimpleSwingWorker # done()' chiama get, esegue il try-catch e ripensa le eccezioni. Ma tu dici che li mangia e non capisco come o perché. Sto contrassegnando questa risposta come quella corretta e quindi semplicemente cancellando il codice; ci sono probabilmente molti modi per farlo bene ma nessuno dei miei colleghi può dirmi perché questo codice non funziona. –

+1

+1 per generici 'SwingWorker ', 'SwingWorker è ben progettato. -100 :-), penso che no, non sono d'accordo, mi mancano i metodi implementati prima che SwingWorker venisse aggiunto alle versioni ufficiali Suns Api, – mKorbel

+0

@TiStrga: il wrapper li lancia, ma nessuno tranne il gestore di eccezioni predefinito di EDT può prenderli. Quindi in pratica: non puoi gestire le eccezioni generate. –

3

Il wrapper sembra funziona come previsto. Tuttavia, la sua implementazione non chiamerà mai done() se si verifica un'eccezione. Questo non è adatto per molti casi. Probabilmente è più semplice chiamare get() in done(). Ciò genererà qualsiasi eccezione che si sia verificata in doInBackground().

Non so come sia strutturato l'esempio, ma non ha funzionato nell'applicazione senza EDT. Quindi avvolgere esecuzione lavoratore in SwingUtilities.invokeLater ha aiutato, vale a dire:

SwingUtilities.invokeLater(new Runnable() { 
    public void run() { 
     new SpecialDataHelper().execute(); 
    } 
}); 

L'esempio seguente fa stampare la traccia dello stack eccezione:

public class Tester { 

    static class SpecialDataHelper extends SimpleSwingWorker { 
     public SpecialDataHelper() { 
     } 
     public Void doInBackground() throws Exception { 
      throw new Exception("test"); 
     } 
     protected void done() { 
     } 
    } 

    public static void main(String[] args) { 
     try{ 
      SwingUtilities.invokeLater(new Runnable() { 
       public void run() { 
        new SpecialDataHelper().execute(); 
       } 
      }); 
     } catch(Exception ex){ 
      ex.printStackTrace(); 
     } 
    } 
} 

Considera anche questo semplice esempio che dimostra come ottenere le eccezioni che si sono verificati in doInBackground() senza utilizzare il wrapper. Il wrapper è solo un aiuto in caso si dimentichi di chiamare get().

import javax.swing.JOptionPane; 
import javax.swing.SwingUtilities; 
import javax.swing.SwingWorker; 

public class Tester { 
    static class Worker extends SwingWorker<Void,Void> { 
     @Override 
     protected Void doInBackground() throws Exception { 
      throw new Exception("test"); 
     } 
     @Override 
     protected void done() { 
      try { 
       get(); 
       JOptionPane.showMessageDialog(null, "Operation completed"); 
      } catch (Exception ex) { 
       JOptionPane.showMessageDialog(null, "Operation failed"); 
      } 
     } 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
      public void run() { 
       new Worker().execute(); 
      } 
     });   
    } 
} 
+2

+1 per 'invokeLater()'. Lanciare un'eccezione esplicita, come hai mostrato, rende l'effetto più facile da vedere. – trashgod

1

Penso che ognuno sta facendo questo troppo complicato. Basta provare questo:

String myResult="notSet"; 
try { 
    // from your example above 
    helper.execute(); // this will call get+done on the actual worker 
    myResult=helper.get(); 
} catch (Exception e) { 
// this section will be invoked if your swingworker dies, and print out why... 
    System.out.println("exception "); 
    e.printStackTrace() ; 
    myResult="Exception "+e.getMessage(); 
} 
return myResult; 

Le eccezioni che si butta ma si mangiano saranno svelate. Due punti spiegheranno perché questo funziona. Uno, si prende solo l'eccezione remota dal thread chiamante whem you .get() il risultato. Più in dettaglio: per rendere l'esempio precedente asincrono, sposta semplicemente .execute() più in alto nel codice. Il momento in cui scoprirai l'eccezione remota è dopo che il thread di asynch ha bombardato e tu.In secondo luogo, prendendo tutte le eccezioni si rileveranno tutte le eccezioni speciali e sottoclasse che potresti aver creato di cui il programma chiamante potrebbe non essere a conoscenza.

Problemi correlati