Hallo,
ich habe wohl ein Problem mit der Synchronisierung von Threads. Vorab: Ich bin ich nicht sonderlich fitt im Umgang mit Threads daher habe ich
mal eine kleine Beispielanwendung gebastelt die das Problem nachstellen kann. Die Beispielanwendung ist natürlich ggü. der echten Anwendung vereinfacht,
kann den "Fehler" aber korrekt reproduzieren.
Zum Problem : Wenn man die Anwendung startet öffnen sich 2 voneinander unabhängige Fenster die jeweils eine lange Verarbeitung simulieren.
Diese langen Verarbeitungen (Threads ... ich nenne sie auch Tasks) können jeweils über einen Cancel-Button in den Fenstern abgebrochen werden.
Wenn man nun folgende Reihenfolge GENAU einhält, kann der/mein Fehler reproduziert werden:
1.) Anwendung starten
2.) Cancel-Button von Fenster1 (LongTaskJFrame-1) anklicken und den Cancel-Dialog STEHENLASSEN!
- Die Verarbeitung wird im Hintergrund korrekt angehalten. Soweit so gut.
3.) Cancel-Button von Fenster2 (LongTaskJFrame-2) anklicken und den Cancel-Dialog ebenfalls STEHENLASSEN!
- Die Verarbeitung wird im Hintergrund auch hier korrekt angehalten.
- MERKE : Würde man jetzt YES oder NO dieses Cancel-Dialog anklicken, würde kein Fehler auftreten. Wir müssen aber hier jetzt mit 4.) weitermachen damit der "Fehler" reproduziert werden kann.
4.) Jetzt den YES oder NO Button vom Cancel-Dialog von Fenster1(!!!) anklicken -> Jetzt müsste je nach Auswahl der Thread/Task von Fenster1 beendet werden oder weiterlaufen. Hier tut sich nun aber überhaupt nichts mehr!
Warum das? Ich hätte jetzt erwartet daß Fenster1 nun beendet oder weiter aktualisiert wird?! Was mache ich hier falsch?
Zur Info : Das Anhalten und Abbrechen der Threads/Tasks findet maßgeblich in den Methoden
Task.checkPaused()
Task.proceed()
LongTaskJFrame -> siehe Konstruktor -> Anonymer ActionListener des Cancel-Buttons
LongTask.run() -> for-Schleife
Sorry für soviel Code, aber wenn man genau hinschaut ist das meiste eigtl. nur 'ne Menge Doku und Swing-Kram
Die Klassen :
Task.java
LongTask.java
LongTaskJFrame.java
ConfirmDialog.java
Main.java
ich habe wohl ein Problem mit der Synchronisierung von Threads. Vorab: Ich bin ich nicht sonderlich fitt im Umgang mit Threads daher habe ich
mal eine kleine Beispielanwendung gebastelt die das Problem nachstellen kann. Die Beispielanwendung ist natürlich ggü. der echten Anwendung vereinfacht,
kann den "Fehler" aber korrekt reproduzieren.
Zum Problem : Wenn man die Anwendung startet öffnen sich 2 voneinander unabhängige Fenster die jeweils eine lange Verarbeitung simulieren.
Diese langen Verarbeitungen (Threads ... ich nenne sie auch Tasks) können jeweils über einen Cancel-Button in den Fenstern abgebrochen werden.
Wenn man nun folgende Reihenfolge GENAU einhält, kann der/mein Fehler reproduziert werden:
1.) Anwendung starten
2.) Cancel-Button von Fenster1 (LongTaskJFrame-1) anklicken und den Cancel-Dialog STEHENLASSEN!
- Die Verarbeitung wird im Hintergrund korrekt angehalten. Soweit so gut.
3.) Cancel-Button von Fenster2 (LongTaskJFrame-2) anklicken und den Cancel-Dialog ebenfalls STEHENLASSEN!
- Die Verarbeitung wird im Hintergrund auch hier korrekt angehalten.
- MERKE : Würde man jetzt YES oder NO dieses Cancel-Dialog anklicken, würde kein Fehler auftreten. Wir müssen aber hier jetzt mit 4.) weitermachen damit der "Fehler" reproduziert werden kann.
4.) Jetzt den YES oder NO Button vom Cancel-Dialog von Fenster1(!!!) anklicken -> Jetzt müsste je nach Auswahl der Thread/Task von Fenster1 beendet werden oder weiterlaufen. Hier tut sich nun aber überhaupt nichts mehr!
Warum das? Ich hätte jetzt erwartet daß Fenster1 nun beendet oder weiter aktualisiert wird?! Was mache ich hier falsch?
Zur Info : Das Anhalten und Abbrechen der Threads/Tasks findet maßgeblich in den Methoden
Task.checkPaused()
Task.proceed()
LongTaskJFrame -> siehe Konstruktor -> Anonymer ActionListener des Cancel-Buttons
LongTask.run() -> for-Schleife
Sorry für soviel Code, aber wenn man genau hinschaut ist das meiste eigtl. nur 'ne Menge Doku und Swing-Kram
Die Klassen :
Task.java
Java:
/**
* Diese Klasse realisiert einen Thread (wir nennen dieses Objekt hier auch Task!) der angehalten und abgebrochen werden kann.
*/
public abstract class Task extends Thread {
// Kann von aussen gesetzt werden um den Thread/Task anzuhalten.
private boolean paused = false;
// Kann von aussen gesetzt werden um den Thread/Task abzubrechen.
private boolean canceled = false;
/**
*
*/
public Task() {
super();
}
/**
* Hiermit kann der Thread/Task angehalten werden.
*/
public final void pause() {
paused = true;
}
/**
* Hiermit kann ermittelt werden ob der Thread/Task angehalten wurde.
*/
public final boolean isPaused() {
return paused;
}
/**
* Hiermit kann der Thread/Task abgebrochen werden.
*/
public final void cancel() {
canceled = true;
}
/**
* Hiermit kann ermittelt werden ob der Thread/Task abgebrochen wurde.
*/
public final boolean isCanceled() {
return canceled;
}
/**
* Hiermit kann der Thread/Task, sofern das Pause Flag gesetzt (true) wurde, angehalten werden.
*/
protected final synchronized void checkPaused() {
if(isPaused()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Hiermit kann der ggf. angehaltene Thread/Task fortgesetzt werden.
*/
public final synchronized void proceed() {
if(isPaused()) {
paused = false;
notify();
}
}
/**
* Wird angestossen wenn der Thread/Task gestartet wird.
*/
public abstract void run();
}
LongTask.java
Java:
/**
* Diese Klasse realisiert nur einen langen ThreadTask/ der angehalten und abgebrochen werden kann.
* Siehe dazu auch die entsprechenden Methoden der Superklasse.
*/
public final class LongTask extends Task {
private LongTaskJFrame frame = null;
/**
* Erzeugt einen langen Thread/Task.
* Der Name wird nur benötigt damit eine Fortschrittsanzeige (ProgressMessage) entsprechend aufbereitet werden kann.
* Wir benötigen hier noch das Fenster weil wir dort die Fortschrittsanzeige anzeigen wollen. zwar nicht sehr elegant
* hier, aber es erfüllt hier seinen Zweck. Normalerweise wird dies über einen Listener o.ä. realisiert. Whatever ...
*/
public LongTask(String name, LongTaskJFrame frame) {
super();
setName(name);
this.frame = frame;
}
/**
*
*/
public void run() {
try {
// Lange Verarbeitung hier jetzt erzeugen.
int maximum = Integer.MAX_VALUE / 70;
for (int i = 0; i < maximum; i++) {
// Ggf. wird hier jetzt angehalten/pausiert.
// Siehe Superklasse !!!
checkPaused();
// Ggf. wird hier jetzt abgebrochen.
// Siehe Superklasse !!!
if (isCanceled()) {
break;
}
// Fortschrittsanzeige aufbereitet anzeigen.
frame.setProgressMessage(getName() + " - > ProgressMessage = " + i);
}
// Fortschrittsanzeige -> ENDE.
frame.setProgressMessage(getName() + " - > FINISHED");
} catch (Exception e) {
e.printStackTrace();
}
}
}
LongTaskJFrame.java
Java:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
/**
* Diese Klasse realisiert eigentlich nur ein Fenster das den Fortschritt eines langen Threads/Tasks anzeigen
* soll. Mit einem Cancel-Button kann der Thread/Task dann angehalten und abgebrochen werden.
*
* Wir starten den langen Thread/Task hier in dieser Klasse. Zwar nicht sehr schick, erfüllt aber hier seinen Zweck.
*/
public final class LongTaskJFrame extends JFrame {
// Ein Label das eine Fortschrittsanzeige (ProgressMessage) anzeigen kann.
private JLabel progressLabel = null;
// Ein Cancel-Button mit dem man den langen Thread/Task anhalten und abbrechen kann.
private JButton cancelButton = null;
// Der lange Thread/Task
private LongTask longTask = null;
/**
*
*/
public LongTaskJFrame(String title) {
super(title);
progressLabel = new JLabel("");
getContentPane().add(progressLabel, BorderLayout.NORTH);
// Cancel-Button mit ActionListener erzeugen.
cancelButton = new JButton("Cancel");
cancelButton.addActionListener(new ActionListener() {
// Es wurd auf den Cancel-Button geklickt.
public void actionPerformed(ActionEvent arg0) {
// 1.) Thread/Task anhalten
longTask.pause();
// 2.) Fragen ob abgebrochen werden soll.
ConfirmDialog confirmDialog = new ConfirmDialog(LongTaskJFrame.this, "Cancel?");
confirmDialog.setVisible(true);
boolean selectedOption = confirmDialog.getSelectedOption();
// Wenn ja Thread/Task abbrechen und dieses Fenster schliessen.
if(selectedOption) {
longTask.cancel();
dispose();
}
// Andernfalls den Thread/Task weiterlaufen lassen.
else {
longTask.proceed();
}
}
});
getContentPane().add(cancelButton, BorderLayout.SOUTH);
// Den langen Thread/Task erzeugen.
longTask = new LongTask("LongTask (" + getTitle() + ")", this);
// Anzeige des Fensters.
setMinimumSize(new Dimension(400, 100));
setPreferredSize(new Dimension(400, 100));
}
/**
* Damit der lange Thread/Task extern gestartet werden kann.
*/
public void startLongTask() {
longTask.start();
}
/**
*
*/
public void setProgressMessage(String progressMessage) {
progressLabel.setText(progressMessage);
}
}
ConfirmDialog.java
Java:
import java.awt.BorderLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
/**
* Diese Klasse realisiert nur einen modalen YES/NO Dialog.
* Die Methode <code>boolean getSelectedOption()</code> liefert dann das Ergebnis.
*/
public final class ConfirmDialog extends JDialog {
private boolean selectedOption = false;
/**
*
*/
public ConfirmDialog(Window owner, String title) {
super(owner);
setTitle(title);
JButton yesButton = new JButton("Yes");
yesButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
selectedOption = true;
dispose();
}
});
getContentPane().add(yesButton, BorderLayout.WEST);
JButton noButton = new JButton("No");
noButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
selectedOption = false;
dispose();
}
});
getContentPane().add(noButton, BorderLayout.EAST);
setModal(true);
setModalityType(ModalityType.DOCUMENT_MODAL);
pack();
setLocationRelativeTo(getOwner());
}
/**
*
*/
public boolean getSelectedOption() {
return selectedOption;
}
}
Main.java
Java:
import java.awt.Point;
/**
* Startklasse der Beispielanwendung.
*/
public class Main {
/**
* Es werden 2 Fenster gestartet die jeweils ein lange (voneinander UNABHÄNGIGE) Verarbeitung simulieren sollen.
* Die Verarbeitung kann jeweils über einen Cancel-Button abgebrochen werden. Damit die Fenster (und ihre
* Cancel-Dialoge) unabhängig voneinander gestartet werden können, wird hier jedes Fenster jeweils in einem
* eigenem Thread gestartet. Würden wir dies hier nicht tun, kommt es seitens Swing zu "fragwürdigem" Verhalten
* bei den modalen Cancel-Dialogen. Wie auch immer, soweit alles (noch) kein Problem.
*
* Spannend wird es wenn wir nun folgendes GENAU IN DER REIHENFOLGE ANKLICKEN :
*
* 1.) Cancel-Button von Fenster1 (LongTaskJFrame-1) anklicken und Cancel-Dialog STEHENLASSEN!
* - Die Verarbeitung wird im Hintergrund korrekt angehalten. Soweit so gut.
*
* 2.) Cancel-Button von Fenster2 (LongTaskJFrame-2) anklicken und Cancel-Dialog ebenfalls STEHENLASSEN!
* - Die Verarbeitung wird im Hintergrund auch hier korrekt angehalten.
* - MERKE : Würde man jetzt YES oder NO dieses Cancel-Dialog anklicken, würde kein Fehler auftreten. Wir
* müssen aber hier jetzt mit 3.) weitermachen damit der Fehler auftritt.
*
* 3.) Jetzt den YES oder NO Button vom Cancel-Dialog von Fenster1(!!!) anklicken -> Jetzt müsste je nach Auswahl
* der Thread/Task von Fenster1 beendet werden oder weiterlaufen. Hier tut sich aber nun nichts mehr!
* Warum das? Ich hätte jetzt erwartet daß Fenster1 nun beendet oder weiter aktualisiert wird?!
*
* Erst wenn der Cancel-Dialog von Fenster2 ebenfalls weggeklickt wurde, nimmt auch der Thread/Task von Fenster1
* wieder seine Arbeit auf.
*
* Was mache ich hier falsch?
*
*/
public static void main(String[] args) {
class Thread1 extends Thread {
public void run() {
// Erstes Fenster erzeugen ...
LongTaskJFrame frame1 = new LongTaskJFrame("LongTaskJFrame-1");
frame1.setLocation(new Point(100, 100));
// ... und anzeigen.
frame1.setVisible(true);
// Lange Verarbeitung jetzt starten.
frame1.startLongTask();
}
}
Thread1 thread1 = new Thread1();
thread1.start();
class Thread2 extends Thread {
public void run() {
// Zweites Fenster erzeugen ...
LongTaskJFrame frame2 = new LongTaskJFrame("LongTaskJFrame-2");
frame2.setLocation(new Point(100, 300));
// ... und anzeigen.
frame2.setVisible(true);
// Lange Verarbeitung jetzt starten.
frame2.startLongTask();
}
}
Thread2 thread2 = new Thread2();
thread2.start();
}
}
Zuletzt bearbeitet von einem Moderator: