Синхронизация потоков SwingWorker - PullRequest
0 голосов
/ 03 февраля 2019

Я создал приложение с графическим интерфейсом, в котором выполняются 4 отдельных потока SwingWorker.Их прогресс указывается отдельными индикаторами и рядом с ним (обновляется динамически).Внизу также есть метка «Общий итог», которая должна быть суммой прогресса всех 4 потоков.Тем не менее, общая сумма не рассчитывается правильно из-за условий гонки.До сих пор я пытался использовать ключевое слово syncrhonized и методы SwingWorker's publish() и process().Ничего не получалось.Есть также кнопки «Пауза» и «Возобновить», которые работают, но создают еще большее расхождение в числе «GrandTotal».

Вот мой код диалога:

public class ThreadTestDialog extends JDialog {
    private JPanel contentPane;
    private JButton buttonStart;
    private JButton buttonPause;
    private JProgressBar progressBar1;
    private JProgressBar progressBar2;
    private JProgressBar progressBar3;
    private JProgressBar progressBar4;
    private JButton buttonResume;
    private JLabel labelThread1;
    private JLabel labelThread2;
    private JLabel labelThread3;
    private JLabel labelThread4;
    private JLabel labelThread1Total;
    private JLabel labelThread2Total;
    private JLabel labelThread3Total;
    private JLabel labelThread4Total;
    private JLabel labelGrandTotal;
    private JLabel labelGrandTotalValue;

    public AppThread thread1 = new AppThread(labelThread1Total, progressBar1, 30, labelGrandTotalValue);
    public AppThread thread2 = new AppThread(labelThread2Total, progressBar2, 75, labelGrandTotalValue);
    public AppThread thread3 = new AppThread(labelThread3Total, progressBar3, 50, labelGrandTotalValue);
    public AppThread thread4 = new AppThread(labelThread4Total, progressBar4, 20, labelGrandTotalValue);

    public ThreadTestDialog() {
        setContentPane(contentPane);
        setModal(true);
        getRootPane().setDefaultButton(buttonStart);

        buttonStart.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onStart();
            }
        });

        buttonPause.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onPause();
            }
        });

        buttonResume.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onResume();
            }
        });

        // call dispose() when cross is clicked
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                dispose();
            }
        });

        // call dispose() on ESCAPE
        contentPane.registerKeyboardAction(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                dispose();
            }
        }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    }

    private void onStart() {
        thread1.execute();
        thread2.execute();
        thread3.execute();
        thread4.execute();
    }

    private void onPause() {
        thread1.setFlag(false);
        thread2.setFlag(false);
        thread3.setFlag(false);
        thread4.setFlag(false);
    }

    private void onResume() {
        int tempValue;

        tempValue = thread1.getValue();
        thread1 = new AppThread(tempValue, labelThread1Total, progressBar1, 30, labelGrandTotalValue);
        thread1.execute();

        tempValue = thread2.getValue();
        thread2 = new AppThread(tempValue, labelThread2Total, progressBar2, 75, labelGrandTotalValue);
        thread2.execute();

        tempValue = thread3.getValue();
        thread3 = new AppThread(tempValue, labelThread3Total, progressBar3, 50, labelGrandTotalValue);
        thread3.execute();

        tempValue = thread4.getValue();
        thread4 = new AppThread(tempValue, labelThread4Total, progressBar4, 20, labelGrandTotalValue);
        thread4.execute();

    }

    public static void main(String[] args) {
        ThreadTestDialog dialog = new ThreadTestDialog();
        dialog.pack();
        dialog.setVisible(true);
        System.exit(0);
    }
}

Вот мой пользовательский класс SwingWorker:

import javax.swing.*;
import java.util.List;

public class AppThread extends SwingWorker<Void, Integer> {
    private int value=0;
    private int sleepTime;
    private JLabel label;
    private JProgressBar progressBar;
    private JLabel grandTotal;
    private boolean flag=true;

    public AppThread (JLabel label, JProgressBar progressBar, int sleepTime, JLabel grandTotal) {
        this.sleepTime = sleepTime;
        this.label = label;
        this.progressBar = progressBar;
        this.grandTotal = grandTotal;
    }

    public AppThread (int value, JLabel label, JProgressBar progressBar, int sleepTime, JLabel grandTotal) {
        this.value = value;
        this.sleepTime = sleepTime;
        this.label = label;
        this.progressBar = progressBar;
        this.grandTotal = grandTotal;
    }

    public Void doInBackground() {
        for (int i = value; i <= 100; i++) {
            if (!flag) break;
            this.value = i;
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            label.setText(Integer.toString(value));
            progressBar.setValue(value);
            progressBar.setStringPainted(true);

            grandTotal.setText(Integer.toString(Integer.parseInt(grandTotal.getText()) + 1));
        }
        return null;
    }

    public void done() {
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public int getValue() {
        return value;
    }

    }
}

Буду крайне признателен за любые предложения о том, как синхронизироватьрабочие потоки, чтобы они корректно обновляли метку "GrandTotal".

1 Ответ

0 голосов
/ 03 февраля 2019

Вы нарушаете правила потоков Swing, обновляя пользовательский интерфейс вне контекста потока диспетчеризации событий, который, как предполагается, SwingWorker поможет вам.

Избегайте передачи ссылок на ваши объекты пользовательского интерфейса вВместо этого SwingWorker, либо используйте метод process для обновления модели состояния или поддержку PropertyChangeListener для косвенного обновления пользовательского интерфейса.

Ниже приведен простой пример, который использует поддержку PropertyChangeListener для обновленияиндикаторы выполнения.

Обратите внимание, я отделил AppThread от пользовательского интерфейса, поэтому теперь пользовательский интерфейс берет на себя ответственность за обновление пользовательского интерфейса.Пример также можно динамически расширять, поэтому вы можете увеличить число AppThread с, просто обновив for-loop, который их создает

ThreadTestDialog

import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JProgressBar;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class ThreadTestDialog extends JDialog {

    private JButton buttonStart;
    private Map<AppThread, JProgressBar> progressBars = new HashMap<>(4);
    private JProgressBar pbGrandTotal;

    public ThreadTestDialog() {
        setModal(true);
        getRootPane().setDefaultButton(buttonStart);

        buttonStart = new JButton("Start");
        buttonStart.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onStart();
            }
        });

        // call dispose() when cross is clicked
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);

        setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridwidth = GridBagConstraints.REMAINDER;

        Random rnd = new Random();
        for (int index = 0; index < 4; index++) {
            AppThread appThread = new AppThread(rnd.nextInt(1000));
            appThread.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    AppThread worker = (AppThread)evt.getSource();
                    String name = evt.getPropertyName();
                    if (name.equals("progress")) {
                        JProgressBar pb = progressBars.get(worker);
                        pb.setValue(worker.getProgress());

                    } else if (name.equals("done")) {
                        // Now you can do something when the worker completes...
                    }
                    updateTotalProgress();
                }
            });
            JProgressBar pb = new JProgressBar(0, 100);

            progressBars.put(appThread, pb);
            add(pb, gbc);
        }

        pbGrandTotal = new JProgressBar(0, progressBars.size() * 100);
        add(pbGrandTotal, gbc);

        add(buttonStart, gbc);
    }

    protected void updateTotalProgress() {
        int totalProgress = 0;
        for (Map.Entry<AppThread, JProgressBar> entry : progressBars.entrySet()) {
            totalProgress += entry.getKey().getProgress();
        }
        pbGrandTotal.setValue(totalProgress);
    }

    private void onStart() {
        for (Map.Entry<AppThread, JProgressBar> entry : progressBars.entrySet()) {
            entry.getKey().execute();
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }
                ThreadTestDialog dialog = new ThreadTestDialog();
                dialog.pack();
                dialog.setVisible(true);
            }
        });
    }
}

AppThread

import javax.swing.SwingWorker;

public class AppThread extends SwingWorker<Void, Integer> {

    private int value = 0;
    private int sleepTime;

    public AppThread(int sleepTime) {
        this.sleepTime = sleepTime;
    }

    public Void doInBackground() {
        for (int i = value; i <= 100; i++) {
            this.value = i;
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            setProgress(value);
        }
        return null;
    }

}
...