10

バックグラウンド タスクでスローされた例外を SwingWorker が処理するというユーザビリティの問題に苦労してきました。たとえば、この SO スレッドで説明されています。そのスレッドは問題の適切な説明を提供しますが、元の例外の回復については議論していません。

渡されたアプレットは、例外を上方に伝搬する必要があります。しかし、私はそれを捕まえることさえできませんでした。このブログ エントリの SimpleSwingWorker ラッパー クラスを使用して、特にこの問題に対処しようとしています。かなり小さなクラスですが、参考のために最後に再掲します。

呼び出しコードは大まかに次のようになります

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 :-(
}

ラッパー:

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

の特徴SimpleSwingWorkerは、実際の SwingWorker のdone()/get()メソッドが自動的に呼び出されることです。これにより、理論的には、バックグラウンドで発生したすべての例外が再スローされます。実際には、何も引っかからず、その理由もわかりません。

参照用の SimpleSwingWorker クラス。簡潔にするために何も省略していません。

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

3 に答える 3

25

SwingWorker は食べませんが、例外を食べるラッパーのことは忘れてください。SwingWorker の使用方法と、バックグラウンド タスクからスローされた特定の例外を処理する方法は次のとおりです。

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
        }
    }
}
于 2012-12-18T23:01:40.957 に答える
3

ラッパーは期待どおりに動作するようです。done()ただし、例外が発生した場合、その実装は呼び出されません。これは多くの場合に適していません。で呼び出す方がおそらく簡単get()ですdone()。これにより、 で発生した例外がスローされdoInBackground()ます。

あなたの例がどのように構成されているかわかりませんが、EDT のないアプリケーションでは機能しませんでした。したがって、ワーカーの実行をラップSwingUtilities.invokeLaterすると、次のように役立ちました。

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

次の例では、例外スタック トレースを出力します。

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

doInBackground()また、ラッパーを使用せずに発生した例外を取得する方法を示すこの簡単な例も検討してください。ラッパーは、呼び出しを忘れた場合の単なるヘルパーです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();
            }
        });         
    }
}
于 2012-12-18T21:46:06.110 に答える