Перечисление проблем с многопоточностью и рисованием Swing:
Весь следующий код вызывается из нити диспетчеризации событий Swing (EDT), и пока все хорошо ...
void Do_Test() {
String Info = "", Result;
Out("Do_Test");
try {
Display_Timer.start();
Timer.Start();
Output_Time("[ 1 ]");
Progress_bar.Set_Progress(1); // OK to call this on the event thread
int Task_Count = 222;
executor = new ThreadPoolExecutor(Executor_Count, Executor_Count * 2, 1,
TimeUnit.SECONDS, new LinkedBlockingQueue());
ArrayList<Future<String>> futures = new ArrayList<>(Task_Count);
Test_Runner A_Runner;
try {
for (int i = 0; i < Task_Count; i++) {
A_Runner = new Test_Runner();
futures.add(executor.submit(A_Runner));
}
executor.shutdown();
ОК, здесь мы начинаем видеть проблемы, когда вы вызываете потенциально длинный цикл EDT, потенциально блокируя его:
while (!executor.isTerminated()) {
executor.awaitTermination(100, TimeUnit.MILLISECONDS);
}
То же самое здесь, так как вызов Future#get()
является блокирующим вызовом.:
for (Future<String> future : futures) {
Result = future.get();
if (Result != null)
Info += Result;
}
Хорошо, теперь мы начинаем видеть действительно необычный код Swing, в котором вы делаете вызовы Swing, включая установку значения JProgressBar off EDT, а затем попытайтесь заставить EDT выполнитьнекоторые картины через paintImmediately(....)
.Выполнение вызовов из EDT может привести к периодическим и непредсказуемым ошибкам, а также может вызвать сбой рисования и иногда зависание графического интерфейса:
class Progress_Bar extends JPanel implements Runnable {
// .....
public void Set_Progress(int Progress) {
if (Progress == 1 || (this.Progress < Progress && Progress <= 100)) {
this.Progress = Progress; // ****** being called off the EDT
b.paintImmediately(0, 0, b.getWidth(), b.getHeight()); // ***** Don't do this
Out(" Progress = " + Progress + " %");
}
// .....
}
public void run() {
try {
while (Progress <= 100) {
if ((Progress == 0 || Progress == 50 || Progress == 100 || Do_Step_B)
&& Last_Progress != Progress) {
// ***** called off the EDT -- don't do this
b.setValue(Progress);
// revalidate();
b.paintImmediately(0, 0, b.getWidth(), b.getHeight()); // **** and don't do this
Last_Progress = Progress;
}
Thread.sleep(200); // Delay the thread
Do_Step_B = (Started_B && (counter++ % Unit_Size == 0));
if (Progress < 100 && Do_Step_B)
Progress++;
}
} catch (Exception e) {
e.printStackTrace();
}
}
// .......
}
Подобные странные дополнения в этом классе, классе Timer, где вы устанавливаете текст JLabel off EDT:
класс Timer расширяет JLabel реализует Runnable {// ....
public void run() {
Thread myThread = Thread.currentThread();
while (Timer_Thread == myThread) {
switch (Format) {
case 1:
Time_Text = Timer.Get_Duration_Hour_Minute_Second();
break;
}
// again this is dangerous code **********
setText(Time_Text);
paintImmediately(0, 0, getWidth(), getHeight());
// ....
То же самое для часовкласс ...
Решения:
Чтобы повторно вызывать код в Swing GUI, используйте javax.swing.Timer
или «Таймер Swing».Для примера использования этого, пожалуйста, посмотрите мою реализацию вашего MCVE, но с использованием Swing Timer для вашего кода многопоточности выше.
Другой код, тот, который вызывает долгосрочные задачи, должен быть выполнен в пределахSwingWorker.Этот рабочий поток обычно взаимодействует с графическим интерфейсом Swing одним из двух способов (или обоими способами) - либо с помощью пары методов публикации / процесса, либо (как в примере ниже), используя PropertyChangeListener, присоединенный к Worker.Рабочие имеют несколько «связанных» свойств, полей, которые уведомляют слушателей об изменениях, включая свойство progress
, которое может содержать значение от 0 до 100, и свойство SwingWorker.StateValue или «состояние»:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
@SuppressWarnings("serial")
public class JavaTest2 extends JPanel {
private static final int W = 800;
private static final int H = 260;
private Action doTestAction = new DoTestAction("Do Test");
private JProgressBar progressBar = new JProgressBar(0, 100);
private MyClockPanel clockPanel = new MyClockPanel();
private MyTimerPanel timerPanel = new MyTimerPanel();
public JavaTest2() {
JPanel topPanel = new JPanel();
topPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 35, 5));
topPanel.add(new JButton(doTestAction));
progressBar.setStringPainted(true);
JPanel progressPanel = new JPanel(new GridBagLayout());
progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1.0;
progressPanel.add(progressBar, gbc);
JLabel progressLabel = new JLabel("Progress", SwingConstants.CENTER);
progressLabel.setFont(new Font("Times New Roman", 0, 20));
progressLabel.setForeground(new Color(8, 68, 128));
JPanel centralPanel = new JPanel(new BorderLayout(5, 5));
centralPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
centralPanel.add(progressLabel, BorderLayout.PAGE_START);
centralPanel.add(progressPanel);
JPanel clockWrapper = new JPanel();
clockWrapper.add(clockPanel);
JPanel bottomPanel = new JPanel();
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.PAGE_AXIS));
bottomPanel.add(timerPanel, BorderLayout.PAGE_START);
bottomPanel.add(clockWrapper, BorderLayout.PAGE_END);
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
add(topPanel, BorderLayout.PAGE_START);
add(bottomPanel, BorderLayout.PAGE_END);
add(centralPanel);
}
@Override
public Dimension getPreferredSize() {
Dimension superSize = super.getPreferredSize();
if (isPreferredSizeSet()) {
return superSize;
} else {
int w = Math.max(superSize.width, W);
int h = Math.max(superSize.height, H);
return new Dimension(w, h);
}
}
private class DoTestAction extends AbstractAction {
private MyWorker myWorker = null;
public DoTestAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
@Override
public void actionPerformed(ActionEvent e) {
if (myWorker != null && myWorker.getState() == SwingWorker.StateValue.STARTED) {
return; // still running
}
timerPanel.start();
progressBar.setValue(0);
myWorker = new MyWorker();
myWorker.addPropertyChangeListener(new WorkerListener());
myWorker.execute();
setEnabled(false);
}
}
class WorkerListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
// if the worker is changing its progress bound property:
if (evt.getPropertyName().equals("progress")) {
int progress = (int) evt.getNewValue();
// just for safety's sake, limit progress to 100 and no more
progress = Math.min(progress, 100);
progressBar.setValue(progress);
} else if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
// else if worker is done
try {
// get the result to at least trap errors
String result = ((MyWorker) evt.getSource()).get();
// can display result in the GUI
timerPanel.stop();
} catch (Exception e) {
// worker's exception is available to the GUI if desired here
e.printStackTrace();
}
progressBar.setValue(100);
doTestAction.setEnabled(true);
}
}
}
private static class MyWorker extends SwingWorker<String, Void> {
private static final int EXECUTOR_COUNT = 12;
private static final int TASK_COUNT = 222;
@Override
protected String doInBackground() throws Exception {
ExecutorService executor = new ThreadPoolExecutor(EXECUTOR_COUNT, EXECUTOR_COUNT * 2, 1,
TimeUnit.SECONDS, new LinkedBlockingQueue<>());
List<Future<String>> futures = new ArrayList<>();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < TASK_COUNT; i++) {
Callable<String> aRunner = new ARunner();
futures.add(executor.submit(aRunner));
}
executor.shutdown();
int index = 0;
for (Future<String> future : futures) {
String result = future.get();
sb.append(result);
sb.append(" ");
index++;
int progress = (100 * index) / TASK_COUNT;
progress = Math.min(progress, 100);
setProgress(progress);
}
return sb.toString();
}
}
private static class ARunner implements Callable<String> {
private static final long SLEEP_TIME = 800;
@Override
public String call() throws Exception {
TimeUnit.MILLISECONDS.sleep(SLEEP_TIME);
return "Foo";
}
}
private static void createAndShowGui() {
JavaTest2 mainPanel = new JavaTest2();
JFrame frame = new JFrame("Java Test 2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
@SuppressWarnings("serial")
class MyClockPanel extends JPanel {
private static final Color FG = new Color(250, 250, 250);
private static final Color BG = new Color(0, 110, 220);
private static final int TIMER_DELAY = 200;
private static final Font FONT = new Font("Monospaced", Font.PLAIN, 16);
private JLabel clockLabel = new JLabel("", SwingConstants.CENTER);
private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E MMM dd yyyy kk:mm:ss");
public MyClockPanel() {
setBackground(BG);
clockLabel.setForeground(FG);
clockLabel.setFont(FONT);
displayDateTime();
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 50, 5, 50));
add(clockLabel);
new javax.swing.Timer(TIMER_DELAY, e -> {
displayDateTime();
}).start();
}
private void displayDateTime() {
LocalDateTime dateTime = LocalDateTime.now();
String text = dateTime.format(formatter);
clockLabel.setText(text);
}
}
@SuppressWarnings("serial")
class MyTimerPanel extends JPanel {
private static final Color FG = new Color(0, 0, 238);
private static final Font FONT = new Font("Times New Roman", 0, 14);
private static final int TIMER_DELAY = 40;
private static final String FORMAT_TXT = "Elapsed Time: %02d:%02d:%02d";
private JLabel timerLabel = new JLabel("", SwingConstants.CENTER);
private LocalDateTime startTime = null;
private Timer timer = null;
public MyTimerPanel() {
timerLabel.setForeground(FG);
timerLabel.setFont(FONT);
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 50, 5, 50));
timerLabel.setText(String.format(FORMAT_TXT, 0, 0, 0));
add(timerLabel);
}
public void start() {
stop();
startTime = LocalDateTime.now();
timer = new Timer(TIMER_DELAY, e -> incrementTime());
timer.start();
}
public void stop() {
if (timer != null && timer.isRunning()) {
timer.stop();
}
}
private void incrementTime() {
LocalDateTime currentTime = LocalDateTime.now();
long hours = ChronoUnit.HOURS.between(startTime, currentTime);
long minutes = ChronoUnit.MINUTES.between(startTime, currentTime) % 60;
long seconds = ChronoUnit.SECONDS.between(startTime, currentTime) % 60;
String text = String.format(FORMAT_TXT, hours, minutes, seconds);
timerLabel.setText(text);
}
}