Auf Swingworker warten?

Thallius

Top Contributor
Hi,

Ich habe folgendes Konstrukt.

Es gibt eine Klasse, nennen wir sie Daten, welche Daten aus dem Internet läd was ja bekanntlich schonmal länger dauern kann. Diese Klasse wird von vielen verschiedenen Stellen meiner App aufgerufen. Die Aufrufende Methode möchte eigentlich nur folgendes machen

Java:
Daten daten = new Daten();
if(daten.load()==false)
     mache irgendwas
sonst mache was anderes.

So nun muss ja in der load() Methode des Klasse Daten aber irgendwie ein Langzeit-Requester mit Abbruch Button geöffnet werden.

Das ist ja auch kein Problem. Ich erzeuge im einfachsten Fall ein Panel mit einem ProgressIndicator und einem AbbruchButton und lasse die Klasse Daten über eine Methode dialogDidCancel() davon erfahren wenn der CancelButton gedrückt wurde.
Dann erzeuge ich einen SwingWorker und lasse im Background die Daten laden. Soweit so gut.
Aber nachdem ich das Panel aufgemacht habe und dem Worker ein execute() geschickt habe, kommt ja meine Load() Methode erstmal wieder zur aufrufenden Methode zurück. Das will ich aber ja nicht. Ich müßte also quasi in der load() MEthode warten bis der Worker fertig ist. Mache ich das aber mit z.B.

Java:
while(!worker.isDone())
    Thread.sleep(250)

was ich eh schon häßlich finde, dann ist aber natürlich auch der Thread blocked und der Dialog wird nicht gezeichnet.

Erkennt ihr mein Dilemma? Wie mache ich sowas richtig?

Danke

Claus
 
Hallo Thallius,
es ist relativ simple als beispiel du hast Klasse B in der Die Datein mit einem Swing worker geladen werden. Und in Klasse A soll etwas passieren wenn der Worker fertig ist. Dann Legen wir in der Klasse A eine Methode an in der alles Steht was passieren soll nach dem der Worker aus B fertig ist. Dann übergeben wir dem Worker aus Klasse B die Klasse A und Overriten die done() im SwingWorker(wird zum Schluss im Worker ausgeführt) darun Rufen wir die vorher angelegte Methode X aus Klasse A auf und Fertig.

Gruß Simon
 
Ja so mache ich es ja im Moment aber es ist halt ätzend weil Das was der worker macht ja nicht nur aus Klasse A aufgerufen wird sonder auch aus Klasse B und C und ...
Das bedeutet ich muss in jeder dieser Klassen diesen Konstrukt aufbauen und vor allem muss ich dem worker dann klarmachen, das die Methode die er aufrufen soll wenn er fertig ist ins verschiedenen Klassen sein kann. Also muss ich da wohl mit irgendwelchen interfaces arbeiten. Das bauscht die Sache ja toral auf.
Ich dachte das müsste doch eigentlich auch irgendwie einfacher gehen ohne das ich eine Verbindung zwischen den Klassen herstellen muss

Gruß

Claus
 
Zuletzt bearbeitet:
Was hälst du von folgendem Konstrukt. Also, wenn ich dich richtig verstanden habe soll die load() Methode blocken. Dann müsstest du die ja per neuem Thread starten, damit der EDT nicht auch blockiert wird. Das wäre die Daten Klasse:
Java:
package test.background;

import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class Daten
{
	private JDialog dialog;
	private SwingWorker<Void, Void> w;
	
	public Daten(Component parent)
	{
		JPanel innerPanel = new JPanel(new GridLayout(2, 1));
		JProgressBar progressBar = new JProgressBar();
		progressBar.setIndeterminate(true);
		innerPanel.add(progressBar);
		
		JButton cancel = new JButton("cancel");
		cancel.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e)
			{
				w.cancel(true);
			}
		});
		innerPanel.add(cancel);
		
		
		dialog = new JDialog();
		dialog.setTitle("Worker Dialog");
		dialog.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e)
			{
				w.cancel(true);
			}
		});
		dialog.setSize(340, 75);
		dialog.setLocationRelativeTo(parent);
		dialog.add(innerPanel);
	}
	
	private void showDialog()
	{
		SwingUtilities.invokeLater(new Runnable() {
			
			@Override
			public void run()
			{
				dialog.setVisible(true);
			}
			
		});
	}
	
	private void closeDialog()
	{
		SwingUtilities.invokeLater(new Runnable() {
			
			@Override
			public void run()
			{
				dialog.dispose();
			}
			
		});
	}
	
	public boolean load()
	{
		showDialog();
		
		w = new SwingWorker<Void, Void>() {
			
			@Override
			protected Void doInBackground() throws Exception
			{
				System.out.println("simulate working...");
				Thread.sleep(10000);
				System.out.println("simulate working done.");
				return null;
			}
			
			@Override
			protected void done()
			{
				closeDialog();
			}
		};
		
		
		try
		{
			w.execute();
			w.get();
			return true;
		}
		catch(CancellationException e)
		{
			//Läuft hier rein, wenn cancel gewählt wird
			return false;
		}
		catch(InterruptedException e)
		{
			//Läuft hier rein, wenn interrupt while waiting?
			e.printStackTrace();
			return false;
		}
		catch(ExecutionException e)
		{
			//Läuft hier rein, wenn eine Exception in doBackground() auftritt
			e.printStackTrace();
			return false;
		}
	}
	
}


BackgroundTest

Java:
package test.background;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class BackgroundTest
{
	
	public static void main(String[] args)
	{
		SwingUtilities.invokeLater(new Runnable() {
			
			@Override
			public void run()
			{
				final JFrame frame = new JFrame("Test Frame");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				frame.setSize(200, 200);
				frame.setLocationRelativeTo(null);
				
				JPanel panel = new JPanel();
				JButton b = new JButton("Go");
				b.addActionListener(new ActionListener() {
					
					@Override
					public void actionPerformed(ActionEvent e)
					{
						Thread t = new Thread() {
							@Override
							public void run()
							{
								Daten daten = new Daten(frame);
								if(daten.load())
								{
									System.out.println("load came back true");
								}
								else
								{
									System.out.println("load came back false");
								}
							}
						};
						t.start();
					}
				});
				panel.add(b);
				
				frame.add(panel, BorderLayout.CENTER);
				frame.setVisible(true);
			}
			
		});
	}
	
}

Gleichzeitiges Verwenden eines Objektes der Daten Klasse aus mehreren Threads wäre so natürlich nicht möglich. Der SwingWorker würde immer durch einen neuen ersetzt und da es nur einen Dialog gibt, wird der erste SwingWorker der fertig wird diesen beenden. Theoretisch zumindest. Ob ein SwingWorker, der keine Referenz mehr hat, aus dem Nirvana noch das dispose auf dem Dialog aufrufen kann bevor er von Garbage Collector abgeräumt wird... Keine Ahnung.
 
Ich benutz zwar kein Swing mehr, aber in JavaFX funktioniert das gute, alte Listener-Prinzip auch noch ohne Probleme:

Deine Datenklasse (hier Reader) erbt von Thread und hält eine Liste vom gewünschten Interface-Typ und ruft dann je nachdem, was in der Datenklasse passiert, die entsprechende Methode des Interfaces auf. Deine Klasse, welche auf die Daten wartet, implementiert das Interface und bekommt dann die Daten, sobald sie vorliegen. Für mich war das immer schon die einfachste Variante, mit Threads umzugehen und meiner Meinung nach auch gut verständlich. Man hat alles selbst in der Hand und weiß, was passiert.

Beispiel:

Interface
Code:
package de.test;

public interface IReader {
	
	public void loaded(Object data);
	public void canceled(Object error);
	
}

Reader
Code:
package de.test;

import java.util.ArrayList;

public class Reader extends Thread {

	private ArrayList<IReader> listener;
	
	public Reader() {
		listener = new ArrayList<IReader>(0);
	}
	
	@Override
	public void run() {
		try {
			Thread.sleep(3000);
			ArrayList<String> data = new ArrayList<String>(0);
			data.add("AAA");
			data.add("BBB");
			data.add("CCC");
			
			for (IReader reader : listener) {
				reader.loaded(data);
			}
		}
		catch (InterruptedException ie) {
			for (IReader reader : listener) {
				reader.canceled(ie);
			}
		}
	}
	
	public void addListener(IReader reader) {
		listener.add(reader);
	}
}

Testklasse implements IReader
Code:
	public void buttonAction(ActionEvent event) {
		if (event.getSource().equals(btnTest)) {
			System.out.println("Button clicked ...");
			
			Reader r = new Reader();
			r.addListener(this);
			r.start();
			//r.interrupt();
		}
	}
	
	@Override
	public void loaded(Object data) {
		if (data != null) {			
			ArrayList<String> list = (ArrayList<String>)data;
			for (String s : list) {
				System.out.println(s);
			}
		}
	}
	
	@Override
	public void canceled(Object error) {
		if (error != null) {
			InterruptedException ie = (InterruptedException)error;
			System.out.println(ie.getMessage());
		}
	}

Über das Interface kannst du dann deine gewünschten Methoden implementieren, z.B. progress(int) für deine Progressbar oder ganz nach Belieben was immer dir so einfällt.
 
Danke,

ja so hab ich es jetzt auch gemacht. Ist halt schon etwas umständlich, wenn man von C++ kommt, wo man einfach einen beliebigen Funcpointer als Callback angeben kann und gut ist.

Gruß

Claus
 

Zurück
Oben