SwingWorker не отвечает

Что я пытаюсь сделать?

По щелчку Start JButton будет выполняться SwingWorker. Внутри метода doInBackground() я передаю каждый индекс arrNames методу publish(), чтобы его можно было отобразить внутри JTextArea.

Что случилось?

Если я не оставлю строку System.out.format("Counter : %d%n", counter); в качестве комментария в моем методе doInBackground() метода SwingWorker, тогда SwingWorker работает, как ожидалось. Хотя если я закомментирую, то SwingWorker перестанет отвечать.

Я делаю что-то не так?


Версия Java:

java version "1.7.0_25"
Java(TM) SE Runtime Environment (build 1.7.0_25-b16)
Java HotSpot(TM) Client VM (build 23.25-b01, mixed mode, sharing)

Вот код, который я использую:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class SwingWorkerExample1
{
    private JLabel statusLabel;
    private JTextArea tArea;
    private JButton startButton;
    private JButton stopButton;

    private BackgroundTask backgroundTask;

    private ActionListener buttonActions =
                            new ActionListener()
    {
        @Override
        public void actionPerformed(ActionEvent ae)
        {
            JButton source = (JButton) ae.getSource();
            if (source == startButton)
            {
                startButton.setEnabled(false);
                stopButton.setEnabled(true);
                backgroundTask = new BackgroundTask();
                backgroundTask.execute();
            }
            else if (source == stopButton)
            {
                backgroundTask.cancel(true);
                stopButton.setEnabled(false);
                startButton.setEnabled(true);
            }
        }
    };

    private void displayGUI()
    {
        JFrame frame = new JFrame("Swing Worker Example");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        JPanel contentPane = new JPanel();
        contentPane.setBorder(
            BorderFactory.createEmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(5, 5));

        statusLabel = new JLabel("Status Bar", JLabel.CENTER);

        tArea = new JTextArea(20, 20);
        tArea.setWrapStyleWord(true);
        tArea.setLineWrap(true);        
        JScrollPane textScroller = new JScrollPane();
        textScroller.setBorder(
            BorderFactory.createTitledBorder("Textual OUTPUT : "));
        textScroller.setViewportView(tArea);

        startButton = new JButton("Start");
        startButton.addActionListener(buttonActions);
        stopButton = new JButton("Stop");
        stopButton.setEnabled(false);
        stopButton.addActionListener(buttonActions);
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(startButton);
        buttonPanel.add(stopButton);

        contentPane.add(statusLabel, BorderLayout.PAGE_START);
        contentPane.add(textScroller, BorderLayout.CENTER);
        contentPane.add(buttonPanel, BorderLayout.PAGE_END);

        frame.setContentPane(contentPane);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private class BackgroundTask extends SwingWorker<Void, String>
    {
        private int counter = 0;

        private String[] arrNames = { "US Rates Strategy Cash",
            "Pavan Wadhwa(1-212) 844-4597", "Srini Ramaswamy(1-212) 844-4983",
            "Meera Chandan(1-212) 855-4555", "Kimberly Harano(1-212) 823-4996",
            "Feng Deng(1-212) 855-2555", "US Rates Strategy Derivatives",
            "Srini Ramaswamy(1-212) 811-4999",
            "Alberto Iglesias(1-212) 898-5442",
            "Praveen Korapaty(1-212) 812-3444", "Feng Deng(1-212) 812-2456",
            "US Rates Strategy Derivatives", "Srini Ramaswamy(1-212) 822-4999",
            "Alberto Iglesias(1-212) 822-5098",
            "Praveen Korapaty(1-212) 812-3655", "Feng Deng(1-212) 899-2222" };

        public BackgroundTask()
        {
            statusLabel.setText((this.getState()).toString());
            System.out.println(this.getState());
        }

        @Override
        protected Void doInBackground()
        {
            statusLabel.setText((this.getState()).toString());
            System.out.println(this.getState());
            while (!isCancelled())
            {
                counter %= arrNames.length;
                //System.out.format("Counter : %d%n", counter);
                publish(arrNames[counter]);
                counter++;
            }
            statusLabel.setText((this.getState()).toString());
            System.out.println(this.getState());
            return null;
        }

        @Override
        protected void process(java.util.List<String> messages)
        {
            for (String message : messages)
                tArea.append(String.format(message + "%n"));
        }
    }

    public static void main(String[] args)
    {
        Runnable runnable = new Runnable()
        {
            @Override
            public void run()
            {
                new SwingWorkerExample1().displayGUI();
            }
        };
        EventQueue.invokeLater(runnable);
    }
}

ИЗМЕНИТЬ 1:

Как было предложено, если я добавлю Thread.sleep(...), это действительно сработает, но выдаст InterruptedException, как показано ниже. Так что трюк работает. Но является ли это законным способом?

C:\Mine\JAVA\J2SE\classes>java SwingWorkerExample1
PENDING
STARTED
java.lang.InterruptedException: sleep interrupted
        at java.lang.Thread.sleep(Native Method)
        at SwingWorkerExample1$BackgroundTask.doInBackground(SwingWorkerExample1.java:108)
        at SwingWorkerExample1$BackgroundTask.doInBackground(SwingWorkerExample1.java:76)
        at javax.swing.SwingWorker$1.call(SwingWorker.java:296)
        at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
        at java.util.concurrent.FutureTask.run(FutureTask.java:166)
        at javax.swing.SwingWorker.run(SwingWorker.java:335)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:724)
DONE

ИЗМЕНИТЬ 2:

Изменено только doInBackground(), что вызвало указанное выше исключение:

@Override
protected Void doInBackground()
{
    Runnable runnable = new Runnable()
    {
        @Override
        public void run()
        {
            statusLabel.setText((BackgroundTask.this.getState()).toString());
        }
    };
    EventQueue.invokeLater(runnable);

    System.out.println(this.getState());
    while (!isCancelled())
    {
        counter %= arrNames.length;             
        //System.out.format("Counter : %d%n", counter);
        publish(arrNames[counter]);
        try
        {Thread.sleep(30);}
        catch(InterruptedException ie)
        {ie.printStackTrace();}
        counter++;
    }
    runnable = new Runnable()
    {
        @Override
        public void run()
        {
            statusLabel.setText((BackgroundTask.this.getState()).toString());
        }
    };
    EventQueue.invokeLater(runnable);
    System.out.println(this.getState());
    return null;
}

person nIcE cOw    schedule 20.07.2013    source источник
comment
Во-первых, вы устанавливаете текст statusLabel в методе doInBackground, что означает, что он обновляется вне EDT. Это должно быть сделано из метода done   -  person MadProgrammer    schedule 20.07.2013
comment
Попробуйте добавить Thread.sleep(30) после вызова публикации....   -  person MadProgrammer    schedule 20.07.2013
comment
нет, это ошибка документации, исправленная, по крайней мере, в jdk7 :-) Плюс ваш statusLabel - это метка, которая никогда не документировалась как потокобезопасная...   -  person kleopatra    schedule 20.07.2013
comment
@kleopatra: Правда, только что понял, что setText() упоминается как связанный с JLabel в моем вопросе :-) Хотя Thread.sleep(...) работает, но выдает InterruptedException, что снова вызывает опасения :(   -  person nIcE cOw    schedule 20.07.2013
comment
@nIcEcOw Вы можете 1- обернуть сон в try-catch или 2- попробовать вместо этого использовать Thread.yield. Проблема в том, что ваш цикл голодает механизм, который вызывает публикацию, предотвращая обновления   -  person MadProgrammer    schedule 20.07.2013
comment
@MadProgrammer: я только что понял, что, идя по дорожке, которую вы мне показали, wait() и notify() могут проделать этот трюк для меня, хотя мне просто интересно, как заставить его работать. Хотя он показывает мне одну строку, хотя мне нужно снова запустить SwingWorker, чтобы увидеть другую строку. Но эта wait()/notify() стратегия действительно работает, все еще работает над ней, чтобы действительно найти выход :-)   -  person nIcE cOw    schedule 20.07.2013
comment
В этом случае я бы поостерегся избегать ожидания/уведомления, если можно - ИМХО   -  person MadProgrammer    schedule 20.07.2013
comment
пожалуйста, посмотрите мои комментарии   -  person mKorbel    schedule 20.07.2013
comment
как на ответ, я забыл ....   -  person mKorbel    schedule 20.07.2013
comment
ну, поток выдает свое собственное прерывание (как вы отменили с помощью cancel(true)) - чего вы ожидаете :-) BTW: лучше всего не использовать любой доступ к качанию в doInbackground (даже если он завернут в invokelater) — механизм, предназначенный для отслеживания изменений статуса, заключается в прослушивании его свойства.   -  person kleopatra    schedule 20.07.2013
comment
Кстати 2: чего вы на самом деле хотите достичь, если думаете, что ждать/уведомлять? Как правило, он не нужен - рабочий подходит под многие нужды (правда, не под все, как мне сказали, не будучи специалистом по треду :-)   -  person kleopatra    schedule 20.07.2013
comment
@kleopatra: я ожидаю, что он по крайней мере будет работать так, как ThreadCounter, по крайней мере, при остановке он не поднимает тревогу, что Я должен беспокоиться о... С wait/notify я надеялся реализовать какие-то методы getter/setter, так что, когда value установлен, поток возьмет его и уведомит другой, чтобы он поместил другое значение на его место, и наоборот -наоборот для другого потока, что-то в строках этого примера Sync.java . :-)   -  person nIcE cOw    schedule 20.07.2013
comment
в этом примере вы не прерываете ни один поток (по крайней мере, afaics :-), в то время как здесь вы делаете - так что еще он должен делать, кроме как бросать? cancel(false) избавится от исключения..   -  person kleopatra    schedule 20.07.2013
comment
@kleopatra: в обоих примерах используется Thread.sleep(1000). И при завершении ни один из них не выбрасывает никаких исключений, а здесь почти то же самое, хотя это исключение вызывается :(   -  person nIcE cOw    schedule 20.07.2013
comment
повторение: здесь вы вызываете worker.cancel(TRUE) - true прерывает текущий поток (то есть поток, который запускает doInBackground) - и это то, о чем говорит исключение говорит тебе...   -  person kleopatra    schedule 20.07.2013
comment
@nIcE cOw этот код никогда не вызывал побочных эффектов от различных JDK или ошибок, чтобы попытаться поместить туда Thread.sleep(int) , а counte уведомляется из других методов, а не из doInBackground   -  person mKorbel    schedule 20.07.2013
comment
@kleopatra: Слишком верно, установка cancel(false) сработала. Пожалуйста, опубликуйте этот комментарий в качестве ответа, я буду признателен за это :-)   -  person nIcE cOw    schedule 20.07.2013


Ответы (3)


если я добавлю Thread.sleep(...), он действительно работает, но выдает InterruptedException

Код, который, по-видимому, создает исключение (скопировано из редактирования OP):

while (!isCancelled()) {
    counter %= arrNames.length;
    // System.out.format("Counter : %d%n", counter);
    publish(arrNames[counter]);
    try {
        Thread.sleep(30); // throws
    } catch (InterruptedException ie) {
        ie.printStackTrace();
    }
    counter++;
}

Причина, однако, заключается в коде, который отменяет worker (в actionListener):

backgroundTask.cancel(true);

который явно сообщает рабочему отменить, .. прерывая поток. Из его API-документа:

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

В качестве отступления: ловить исключение и ничего не делать (тем самым фактически игнорируя прерывание) — не лучшая идея. Вероятно, в этом случае не так уж много вреда - из-за проверки отмененного статуса. Типичные рабочие реализации либо перехватывают и возвращают после некоторой внутренней очистки, если это необходимо, либо вообще не обрабатывают это.

person kleopatra    schedule 20.07.2013
comment
Что ж, спасибо за это, все еще изучаю новые штуки, время от времени, без сомнения, определенные штуки, которые не обязательно будут реализованы в производственном коде. Но рад, что вы указали на это, как всегда +1 за это :-) - person nIcE cOw; 20.07.2013

Усиливая другие ответы, не обновляйте графический интерфейс из фонового потока, который блокирует EDT, и не пытайтесь избежать проблемы с invokeLater(). Вместо этого publish() получите желаемый результат и обновите statusLabel и tArea в process(), как предлагается ниже. Для тестирования Thread.sleep(100) имитирует небольшую задержку, но вы можете использовать Thread.yield(), как показано здесь. Вы также можете обновить графический интерфейс в PropertyChangeListener, как показано здесь.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.*;
import javax.swing.*;

public class SwingWorkerExample1 {

    private JLabel statusLabel;
    private JTextArea tArea;
    private JButton startButton;
    private JButton stopButton;
    private BackgroundTask backgroundTask;
    private ActionListener buttonActions = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent ae) {
            JButton source = (JButton) ae.getSource();
            if (source == startButton) {
                startButton.setEnabled(false);
                stopButton.setEnabled(true);
                backgroundTask = new BackgroundTask();
                backgroundTask.execute();
            } else if (source == stopButton) {
                backgroundTask.cancel(true);
                stopButton.setEnabled(false);
                startButton.setEnabled(true);
            }
        }
    };

    private void displayGUI() {
        JFrame frame = new JFrame("Swing Worker Example");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        JPanel contentPane = new JPanel();
        contentPane.setBorder(
            BorderFactory.createEmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(5, 5));

        statusLabel = new JLabel("Status Bar", JLabel.CENTER);

        tArea = new JTextArea(20, 20);
        tArea.setWrapStyleWord(true);
        tArea.setLineWrap(true);
        JScrollPane textScroller = new JScrollPane();
        textScroller.setBorder(
            BorderFactory.createTitledBorder("Textual OUTPUT : "));
        textScroller.setViewportView(tArea);

        startButton = new JButton("Start");
        startButton.addActionListener(buttonActions);
        stopButton = new JButton("Stop");
        stopButton.setEnabled(false);
        stopButton.addActionListener(buttonActions);
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(startButton);
        buttonPanel.add(stopButton);

        contentPane.add(statusLabel, BorderLayout.PAGE_START);
        contentPane.add(textScroller, BorderLayout.CENTER);
        contentPane.add(buttonPanel, BorderLayout.PAGE_END);

        frame.setContentPane(contentPane);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private class BackgroundTask extends SwingWorker<Void, String> {

        private int counter = 0;
        private String[] arrNames = {"US Rates Strategy Cash",
            "Pavan Wadhwa(1-212) 844-4597", "Srini Ramaswamy(1-212) 844-4983",
            "Meera Chandan(1-212) 855-4555", "Kimberly Harano(1-212) 823-4996",
            "Feng Deng(1-212) 855-2555", "US Rates Strategy Derivatives",
            "Srini Ramaswamy(1-212) 811-4999",
            "Alberto Iglesias(1-212) 898-5442",
            "Praveen Korapaty(1-212) 812-3444", "Feng Deng(1-212) 812-2456",
            "US Rates Strategy Derivatives", "Srini Ramaswamy(1-212) 822-4999",
            "Alberto Iglesias(1-212) 822-5098",
            "Praveen Korapaty(1-212) 812-3655", "Feng Deng(1-212) 899-2222"};

        public BackgroundTask() {
            statusLabel.setText((this.getState()).toString());
        }

        @Override
        protected Void doInBackground() {
            while (!isCancelled()) {
                counter %= arrNames.length;
                publish(arrNames[counter]);
                counter++;
                try {
                    Thread.sleep(100); // simulate latency
                } catch (InterruptedException ex) {
                    publish("Cancelled: " + isCancelled());
                }
            }
            return null;
        }

        @Override
        protected void process(java.util.List<String> messages) {
            statusLabel.setText((this.getState()).toString());
            for (String message : messages) {
                tArea.append(String.format(message + "%n"));
            }
        }
    }

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                new SwingWorkerExample1().displayGUI();
            }
        };
        EventQueue.invokeLater(runnable);
    }
}
person trashgod    schedule 20.07.2013
comment
Ахха, я только что понял, что все имели в виду под «Не обновлять графический интерфейс из фонового потока» :-), так как я не смог понять, куда поместить эту штуку тогда, если не в doInBackground() (Как зафиксировать эти СОСТОЯНИЯ для рабочего Нить). Итак, я предполагаю, что теперь, когда вы переместили одно выражение в метод process() и поймали состояние DONE, я могу либо переопределить done() и поместить в него одно, либо просто поместить его в ActionListener, связанный с Stop Button. Я надеюсь, что я прямо сейчас !!! - person nIcE cOw; 20.07.2013
comment
Теперь понял, что механизм Exception Handling умеет ловить DONE STATE, выглядит интересно, что даже после отмены можно вызывать publish() и косвенно process :-) - person nIcE cOw; 20.07.2013
comment
Правильный; все, что отправляется через publish() в фоновом потоке, будет видно в process(), работающем в EDT. - person trashgod; 20.07.2013
comment
+1 за ответ на основной вопрос :-) Интересно, однако, о рекомендации Thread.yield() (который в настоящее время, кажется, распространяется), поскольку документ API явно упоминает not для его использования: это редко целесообразно использовать этот метод. - не могли бы вы немного уточнить, почему вы используете это здесь? - person kleopatra; 21.07.2013
comment
@kleopatra: хорошее замечание по поводу пересмотренного API: 6, 7. В этом случае yield() полезен для целей отладки или тестирования, чтобы смоделировать задержку, присущую типичной проблеме реального мира. Без хотя бы yield() фоновый поток вызывает зависание и насыщает Timer, упомянутый здесь. - person trashgod; 21.07.2013
comment
мой плохой: перечитывая ваш ответ, я неправильно понял ваше предложение ранее (и не заглянул в код) - извините. Проголосовал бы еще раз, если можно :-) - person kleopatra; 21.07.2013

Комментарии

  • @kleopatra это ошибка документации, исправленная по крайней мере в jdk7 :-), пожалуйста, в каком из JDK эти ошибки показаны как из карусели ....,

  • ожидание/уведомление для потока, SwingWorker — это будущее, очень плохо реализовано, значит без уведомлений, вы что-то кладете в трубку и ждете на другой стороне,

  • похоже, что между одной и второй стороной этой трубы ничего нет, поэтому я пытался вызвать Thread, Runnable, Executor(Runnable) из doInBackground и игнорировать публикацию, процесс, setProcess

  • еще одна забавная проблема заключается в том, чтобы получить () и исключение (я) все исключения, а не только 1-е. с одной и второй стороны этой трубы

  • есть два способа использования SwingWokrer

    1. старайтесь избегать использования SwingWorker

    2. используйте doInBackground в качестве моста для рабочего потока, для вывода используйте публикацию, обработку, setProcess, ожидание done() и используйте done() в качестве уведомителя для получения исключения, уведомителя о том, что SwingWorker завершился

person mKorbel    schedule 20.07.2013
comment
+1, мне понравился этот второй пункт, как уже указал @MadProgrammer, я все еще думаю, почему этот подход плохой. Насколько я понял до сих пор, ставить EDT на wait(), чтобы можно было notified о каком-то наступлении события, плохо во всех отношениях. Тогда получается, что почему бы не оставить эту System.out.println(...) штуку как есть, по крайней мере, тогда вся штука работает как положено :-) - person nIcE cOw; 20.07.2013