SwingWorker: когда именно вызывается метод done?

Javadoc метода done() метода SwingWorker:

Выполняется в потоке отправки событий после завершения метода doInBackground.

У меня есть подсказки, что это неверно в случае отмененного работника.
Done вызывается в каждом случае (обычное завершение или отмена), но когда cancelled он не ставится в очередь в EDT, как это происходит для нормального завершения.

Есть ли более точный анализ того, когда вызывается done в случае отмены SwingWorker?

Уточнение: этот вопрос НЕ о том, как cancel и SwingWorker. Здесь предполагается, что SwingWorker отменяется правильным образом.
И речь идет НЕ о том, что поток все еще работает, когда он должен быть завершен.


person AgostinoX    schedule 01.06.2011    source источник
comment
Почему бы вам не взглянуть на источник?   -  person Laurent Pireyn    schedule 01.06.2011
comment
Это странно. У меня есть Java 6, и в документации говорится, что SwingWorker включен в Java 6, но я не могу найти его в своем rt.jar! :-/   -  person Aaron Digulla    schedule 01.06.2011
comment
Ах, это в src.zip, но нет файла .class. Здорово....   -  person Aaron Digulla    schedule 01.06.2011
comment
@AgostinoX, пожалуйста, проверьте эти два потока stackoverflow.com/questions/6171414/ stackoverflow.com/questions/6051755/, а затем задайте вопрос, в любом случае учебник по SwingWorker содержит примеры download.oracle.com/javase/tutorial/uiswing/concurrency/   -  person mKorbel    schedule 01.06.2011
comment
@mKorblel: я ОЧЕНЬ внимательно прочитал руководство по SwingWorker, а также прочитал документацию по Java. Если у вас тоже есть, вы знаете, что в учебнике показан только базовый пример отмены и не вдается в подробности.   -  person AgostinoX    schedule 01.06.2011
comment
@AgostinoX снова в этом потоке реализован метод PropertyChangeListener " title="как обмениваться данными с классом two2 swingworker в java"> stackoverflow.com/questions/6171414/   -  person mKorbel    schedule 01.06.2011
comment
@mKorblel: извините, mKorblel, вы внимательно прочитали мой вопрос?   -  person AgostinoX    schedule 01.06.2011
comment
Связано: stackoverflow.com/questions/24958793/   -  person kmort    schedule 28.01.2015


Ответы (6)


Когда поток отменяется через

myWorkerThread.cancel(true/false);

метод done (как ни странно) вызывается самим методом отмены.

То, что вы можете ожидать, но на самом деле НЕ ПРОИСХОДИТ:
- вы вызываете отмену (с помощью mayInterrupt или нет)
- отмена устанавливаете отмену потока
- doInBackground завершает работу
- готово вызывается*
(* выполненное ставится в очередь в EDT, это означает, что если EDT занят, это происходит ПОСЛЕ того, как EDT заканчивает свою работу)

Что на самом деле происходит:
— вы вызываете отмену (с помощью mayInterrupt или без нее)
— отмена устанавливаете отмену потока
— завершение вызывается как часть кода отмены*
— doInBackground будет выйти, когда он завершит свой цикл
(*готово не ставится в очередь в EDT, а вызывается в вызове отмены, поэтому оно оказывает очень непосредственное влияние на EDT, что часто является графическим интерфейсом)

Привожу простой пример, подтверждающий это.
Скопируйте, вставьте и запустите.
1. Я генерирую исключение времени выполнения внутри done. Поток стека показывает, что done вызывается отменой.
2. Примерно через 4 секунды после отмены вы получите приветствие от doInBackground, которое быстро доказывает, что done вызывается перед выходом из потока.

import java.awt.EventQueue;
import javax.swing.SwingWorker;

public class SwingWorker05 {
public static void main(String [] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            try {
            W w = new W();
            w.execute();
            Thread.sleep(1000);
            try{w.cancel(false);}catch (RuntimeException rte) {
                rte.printStackTrace();
            }
            Thread.sleep(6000);
            } catch (InterruptedException ignored_in_testing) {}
        }

    });
}

public static class W extends SwingWorker <Void, Void> {

    @Override
    protected Void doInBackground() throws Exception {
        while (!isCancelled()) {
            Thread.sleep(5000);
        }
        System.out.println("I'm still alive");
        return null;
    }

    @Override
    protected void done() {throw new RuntimeException("I want to produce a stack trace!");}

}

}
person AgostinoX    schedule 01.06.2011
comment
+1 Тот же (удивительный) результат, что и у меня примерно в то же время ;-) - person Howard; 01.06.2011
comment
Таким образом, может случиться так, что вы опубликуете/обработаете результаты после done. - person Howard; 01.06.2011
comment
возможно, это может помочь вам stackoverflow.com/questions/6113944/ - person mKorbel; 01.06.2011
comment
@Howard: куда мы идем дальше? Возникает две проблемы: 1) почему недокументировано 2) самое главное. если в конце концов doInBackground выпустит конфиденциальные ресурсы, как я могу быть уверен, что это закончилось? Вы предлагаете открыть новый вопрос? (пожалуйста, кто читает и не понял именно эту тонкую особенность, это НЕ уже везде обсуждавшееся завершение потока SwingWorker) - person AgostinoX; 01.06.2011
comment
@AgostinoX, пожалуйста, смотрите мой вопрос stackoverflow.com/questions/7053865/ - person mKorbel; 14.08.2011
comment
Это было принято Oracle как ошибка: bugs.sun.com/bugdatabase/ view_bug.do?bug_id=6826514 - person Duncan Jones; 27.02.2012
comment
@Дункан, спасибо! это очень интересно, это собственно и есть этот вопрос. Если бы я знал это в то время, когда задавал этот вопрос, не было бы всей этой дискуссии. - person AgostinoX; 29.02.2012
comment
Документация SwingWorker предлагает вам проверить isCancelled(), чтобы убедиться, что флаг отмены был активирован вызовом метода отмены. Так что я думаю, что это зависит от вас. Если вы удалите спящий режим, программы завершатся... это означает, что все еще работающий doInBackground остается игнорируемым потоком для завершения. Вся работа по очистке свинг-воркера должна выполняться методом done. - person Victor; 01.08.2013
comment
Этот отчет об ошибке не был обновлен, но в Java 7 вызов #cancel() не вызывает #done() (Oracle JDK) - person searchengine27; 26.04.2016
comment
Извини, @searchengine27, но это не так. Глядя на исходный код Java 8 JRE прямо сейчас; SwingWorker#cancel() вызывает FutureTask#cancel(), который, если сразу не возвращает false, вызывает закрытый вспомогательный метод finishCompletion(), который безоговорочно вызывает FutureTask#done(). И анонимный подкласс SwingWorker от FutureTask переопределяет этот done() для вызова собственного частного SwingWorker#doneEDT(), который безоговорочно вызывает или ставит в очередь свой собственный done() в EDT. - person Ti Strga; 25.05.2018

done() вызывается в любом случае, отменяется ли рабочий процесс или он завершается нормально. Тем не менее, есть случаи, когда doInBackground все еще выполняется, а метод done уже вызывается (это делается внутри cancel(), независимо от того, завершен ли поток). Простой пример можно найти здесь:

public static void main(String[] args) throws AWTException {
    SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>() {

        protected Void doInBackground() throws Exception {
            System.out.println("start");
            Thread.sleep(2000);
            System.out.println("end");
            return null;
        }

        protected void done() {
            System.out.println("done " + isCancelled());
        }
    };
    sw.execute();
    try {
        Thread.sleep(1000);
        sw.cancel(false);
        Thread.sleep(10000);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }

Таким образом, может быть так, что done вызывается до завершения doInBackground.

person Howard    schedule 01.06.2011
comment
В порядке. код почти такой же, как я предоставил. Более того, я доказал, что done происходит внутри отмены с помощью трюка с трассировкой стека. - person AgostinoX; 01.06.2011

что-то возможно, другое может быть иллюзией

действительно хороший выход

run:
***removed***
java.lang.RuntimeException: I want to produce a stack trace!
        at help.SwingWorker05$W.done(SwingWorker05.java:71)
        at javax.swing.SwingWorker$5.run(SwingWorker.java:717)
        at javax.swing.SwingWorker.doneEDT(SwingWorker.java:721)
        at javax.swing.SwingWorker.access$100(SwingWorker.java:207)
        at javax.swing.SwingWorker$2.done(SwingWorker.java:284)
        at java.util.concurrent.FutureTask$Sync.innerCancel(FutureTask.java:293)
        at java.util.concurrent.FutureTask.cancel(FutureTask.java:76)
        at javax.swing.SwingWorker.cancel(SwingWorker.java:526)
        at help.SwingWorker05$1.run(SwingWorker05.java:25)
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
I'm still alive
Thread Status with Name :SwingWorker1, SwingWorker Status is STARTED
SwingWorker by tutorial's background process has completed
Thread Status with Name :SwingWorker1, SwingWorker Status is DONE
Thread Status with Name :look here what's possible with SwingWorker, SwingWorker Status is STARTED
BUILD SUCCESSFUL (total time: 10 seconds)

от

import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.SwingWorker;

public class SwingWorker05 {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            public void run() {
                try {
                    W w = new W();
                    w.addPropertyChangeListener(
                            new SwingWorkerCompletionWaiter("look here what's possible with SwingWorker"));
                    w.execute();
                    Thread.sleep(1000);
                    try {
                        w.cancel(false);
                    } catch (RuntimeException rte) {
                        rte.printStackTrace();
                    }
                    Thread.sleep(6000);
                } catch (InterruptedException ignored_in_testing) {
                }
            }
        });

        final MySwingWorker mySW = new MySwingWorker();
        mySW.addPropertyChangeListener(new SwingWorkerCompletionWaiter("SwingWorker1"));
        mySW.execute();
    }

    private static class MySwingWorker extends SwingWorker<Void, Void> {

        private static final long SLEEP_TIME = 250;

        @Override
        protected Void doInBackground() throws Exception {
            Thread.sleep(SLEEP_TIME);
            return null;
        }

        @Override
        protected void done() {
            System.out.println("SwingWorker by tutorial's background process has completed");
        }
    }

    public static class W extends SwingWorker {

        @Override
        protected Object doInBackground() throws Exception {
            while (!isCancelled()) {
                Thread.sleep(5000);
            }

            System.out.println("I'm still alive");
            return null;
        }

        @Override
        protected void done() {
            System.out.println("***remove***");
            throw new RuntimeException("I want to produce a stack trace!");
        }
    }

    private static class SwingWorkerCompletionWaiter implements PropertyChangeListener {

        private String str;

        SwingWorkerCompletionWaiter(String str) {
            this.str = str;
        }

        @Override
        public void propertyChange(PropertyChangeEvent event) {
            if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) {
                System.out.println("Thread Status with Name :" + str + ", SwingWorker Status is " + event.getNewValue());
            } else if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.PENDING == event.getNewValue()) {
                System.out.println("Thread Status with Mame :" + str + ", SwingWorker Status is " + event.getNewValue());
            } else if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.STARTED == event.getNewValue()) {
                System.out.println("Thread Status with Name :" + str + ", SwingWorker Status is " + event.getNewValue());
            } else {
                System.out.println("Thread Status with Name :" + str + ", Something wrong happends ");
            }
        }
    }
}
person mKorbel    schedule 01.06.2011
comment
@mKorbel. Я работаю над вашим образцом. Первый вопрос. Поскольку моя реализация методов SwingWorker состоит из 6 строк, можете ли вы указать, где неправильная реализация? увидим, что речь идет о том, когда вызывается отмена. Я показал вне всяких сомнений, что да, done вызывается в каждом случае (в EDT, как и ожидалось!) И в случае отмены он не ставится в очередь EDT, а вызывается как часть вызова cancel(...), возможно, перед выходом из метода doInBackground. Сейчас я проверяю точное поведение PropertyChangeListener, я дам вам знать. - person AgostinoX; 02.06.2011
comment
@AgostinoX :-) Поскольку моя реализация методов SwingWorker состоит из 6 строк :-) нет, это не просто ванильный внутренний класс или пустоты 1st. ожидание таймера, 2-й. для 1-го конца, ничего особенного, небольшая ошибка..., давайте вернемся к началу (Coldplay), самое главное, если вы хотите поиграть с SwingWorker, тогда ваш код может подтвердить свой вывод непосредственно в GUI (JTextArea), в противном случае не не имеет смысла, потому что System.out.print() работает во всех случаях (+ - CitiBus), удачи - person mKorbel; 03.06.2011
comment
В порядке. так что класс W правильный. Не могли бы вы удалить комментарий о неправильной реализации классов? Спасибо. Тогда вы говорите, что проблема в ожидании таймера? Какой таймер? Возможно, вы имеете в виду функцию Thread.sleep. Я сделал класс, который каждый может использовать для воспроизведения конкретной проблемы, о которой я говорю, поэтому мне нужно что-то, что зависает на несколько секунд, например, медленное соединение с базой данных. Я уверен, что это можно сделать лучше, поэтому я рад услышать от вас. - person AgostinoX; 03.06.2011
comment
@@AgostinoX уверен, что я удалю это только по вашему запросу, но ничего не изменится в том, что правильный синтаксис должен включать расширения SwingWorker‹Void, Void›/‹Void, String›/‹Void, Double›...‹ Void, Icon›, иначе это не SwingWorker, как мы знали, а только какой-то класс, который требует некоторых методов, но без заявленных функций SWingWorkers ..., так что, наконец, теперь я ушел из этой темы - person mKorbel; 03.06.2011
comment
@mKorbel.I, по вашей просьбе, добавьте ‹Void, Void› в мою реализацию SwingWorker, так как это поможет людям, привыкшим видеть дженерики, хорошо понять класс. Но я хочу отметить, что функциональность обеспечивается не дженериками, а расширением класса или реализацией интерфейса. Дженерики — это способ избежать приведения типов. Более того, поскольку они реализуются путем стирания, скомпилированный код с или без него одинаков; мы говорим об удобстве и привычках, а не о правильности. - person AgostinoX; 03.06.2011

Пока SwingWorker не будет исправлен http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6826514 Вот простая (проверенная) версия с базовыми (похожими) функциями, затем SwingWorker

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tools;

import java.util.LinkedList;
import java.util.List;
import javax.swing.SwingUtilities;

/**
 *
 * @author patrick
 */
public abstract class MySwingWorker<R,P> {

    protected abstract R doInBackground() throws Exception;
    protected abstract void done(R rvalue, Exception ex, boolean canceled);
    protected void process(List<P> chunks){}
    protected void progress(int progress){}

    private boolean cancelled=false;
    private boolean done=false;
    private boolean started=false;
    final private Object syncprogress=new Object();
    boolean progressstate=false;
    private int progress=0;
    final private Object syncprocess=new Object();
    boolean processstate=false;
    private LinkedList<P> chunkes= new LinkedList<>();

    private Thread t= new Thread(new Runnable() {
        @Override
        public void run() {
            Exception exception=null;
            R rvalue=null;
            try {
                rvalue=doInBackground();
            } catch (Exception ex) {
                exception=ex;
            }

            //Done:
            synchronized(MySwingWorker.this)
            {
                done=true;
                final Exception cexception=exception;
                final R crvalue=rvalue;
                final boolean ccancelled=cancelled;

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        done(crvalue, cexception, ccancelled);
                    }
                });
            }

        }
    });    

    protected final void publish(P p)
    {
        if(!Thread.currentThread().equals(t))
            throw new UnsupportedOperationException("Must be called from worker Thread!");
        synchronized(syncprocess)
        {
            chunkes.add(p);
            if(!processstate)
            {
                processstate=true;
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        List<P> list;
                        synchronized(syncprocess)
                        {
                            MySwingWorker.this.processstate=false;
                            list=MySwingWorker.this.chunkes;
                            MySwingWorker.this.chunkes= new LinkedList<>();
                        }
                        process(list);
                    }
                });
            }
        }
    }

    protected final void setProgress(int progress)
    {
        if(!Thread.currentThread().equals(t))
            throw new UnsupportedOperationException("Must be called from worker Thread!");
        synchronized(syncprogress)
        {
            this.progress=progress;
            if(!progressstate)
            {
                progressstate=true;
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        int value;
                        //Acess Value
                        synchronized(syncprogress)
                        {
                            MySwingWorker.this.progressstate=false;
                            value=MySwingWorker.this.progress;
                        }
                        progress(value);
                    }
                });
            }
        }
    }

    public final synchronized void execute()
    {
        if(!started)
        {
            started=true;
            t.start();
        }
    }

    public final synchronized boolean isRunning()
    {
        return started && !done;
    }

    public final synchronized boolean isDone()
    {
        return done;
    }

    public final synchronized boolean isCancelled()
    {
        return cancelled;
    }

    public final synchronized void cancel()
    {
        if(started && !cancelled && !done)
        {
            cancelled=true;
            if(!Thread.currentThread().equals(t))
                t.interrupt();
        }
    }

}
person pknoe3lh    schedule 21.09.2013

Из документов Java: отмена (логическое значение mayInterruptIfRunning) «mayInterruptIfRunning — true, если поток, выполняющий эту задачу, должен быть прерван; в противном случае разрешено выполнение текущих задач»

Если вы вызываете отмену (true) вместо отмены (false), это, кажется, ведет себя так, как вы ожидаете.

Я не видел, чтобы done() вызывал EDT с помощью EventQueue.isDispatchThread()

person user1610348    schedule 25.11.2012

ЕСЛИ вы используете return Void: ...@Override public Void doInBackground(){...

done() вызывается после завершения doInBackground().

ЕСЛИ вы не используете return Void: ...@Override public boolean doInBackground(){...

done() игнорируется, и вы знаете, что закончили, потому что у вас есть возврат.

person IvanVar    schedule 27.04.2017
comment
Добро пожаловать в СО. Этот пост не соответствует нашим стандартам качества. Как писать качественные ответы, читайте здесь. - person Rahul Sharma; 27.04.2017