[Java Swing] Komunikacja między wątkami i komponentami Swing

0

Witam,

Mam za zadanie stworzyć prosty interfejs użytkownika, w którym naciśnięcie przycisku uruchamia kilka wątków, które w wypisują odpowiednie treści w polu tekstowym. Jak się tworzy komunikację między wątkami (czy szerzej, klasami spoza klasy implementującej okno), a komponentami okna?

0

Cześć.

Watek jest obiektem klasy rozszerzającej Thread, lub implementującej interfejs Runnable. (ja używam tego drugiego sposobu).

Ponieważ jest to "zwykła" klasa, to możesz do niej przekazać obiekt pola tekstowego, lub całej formatki przy pomocy konstruktora, lub settera.
Np.

Masz obiekt okno, który ma w sobie pole.
Tworzysz obiekt Wątek, implementujący Runnable.
Wywolujesz Watek.setPole(pole);
A potem:
Thread starter = new Thread(Wątek);
starter.start();

W metodzie Wątek.run() robisz pole.setText("hello world");

i tyle.

Pozdrawiam.

0

Hej

Jeżeli zamierzasz z wątków odwoływać się np do tego samego komponentu, warto utworzyć metodę oznaczoną słowem synchronized np.

class ObslugaPrzyciskow implements Runnable
{
private JTextField field;
public ObsługaPrzyciskow(JTextField jt)
{
field = jt;
}

public void run() //implementacja metody z interfejsu Runnable, chyba tak się nazywa
{
for(int i=0 ; i<1000 ; i++)
{
update();
try{ Thread.sleep(1000);} catch (Exception ex){}
}
}

private synchronized void update()
{
 field.setText(field.getText() +" " + "jakiś nowy tekst");
}

}
 

Napisałem to z głowy to mogłem gdzieś się pomylić, ale mniej więcej taka koncepcja.

Potem tworzysz

Thread t = new Thread(new ObsługaPrzyciskow());
t.start();
0

Jeśli chodzi o Swinga to nie należy używać zwykłych wątków tylko ładować SwingWorkery: http://download.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html . GUI powinno aktualizować tylko z poziomu EDT, inaczej mogą powstawać trudne do zlokalizowania błędy. Swing nie jest thread-safe, nie udało się zrobić swego czasu wielowątkowego Swinga i jak na razie nic mi nie wiadomo, żeby miało się to zmienić.

0
donkey7 napisał(a)

Jeśli chodzi o Swinga to nie należy używać zwykłych wątków tylko ładować SwingWorkery: http://download.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html . GUI powinno aktualizować tylko z poziomu EDT, inaczej mogą powstawać trudne do zlokalizowania błędy. Swing nie jest thread-safe, nie udało się zrobić swego czasu wielowątkowego Swinga i jak na razie nic mi nie wiadomo, żeby miało się to zmienić.

oracle.com napisał(a)

SwingWorker is only designed to be executed once. Executing a SwingWorker more than once will not result in invoking the doInBackground method twice.

A istnieje jakaś wersja która by działała cały czas w tle? Ja potrzebuję stworzyć wątek który cały czas wypisuje przypisaną mu literę synchronizując się z innymi wątkami (np. żeby A i B były wyświetlane na zmianę) więc SwingWorker niespecjalnie się do tego nadaje.

0

Ale przecież:

  1. SwingWorker może działać dowolnie długo,
  2. Możesz utworzyć i uruchomić naraz wiele SwingWorkerów,
  3. Możesz zrobić podklasę rozszerzającą SwingWorkera i wrzucić jej tam jakieś obiekty do synchronizacji, mogą to być nawet inne SwingWorkery,

To powinno wystarczyć.

0

Hm, to powiedz mi może, co ja źle robię. Stworzyłem coś takiego:

public class Mainwindow extends JFrame {
     private class LetterWriter extends SwingWorker<Object, Character> {
		Character Letter;
		
		public LetterWriter(Character let){
			Letter = let;
		}
		
		@Override
		protected Object doInBackground() throws Exception {
			while(true){
				if(Kontrolka.CheckAvalability(Letter)) {
					System.out.println(Letter.toString() + " Printing");
					publish(Letter);
					Kontrolka.notifyAll();
				} else {
					System.out.println(Letter.toString() + " Waiting...");
					Kontrolka.wait();
				}
			}
		}
		
		protected void process(List<Character> chunks){
			for(Character let : chunks){
				OutputTextArea.append(let.toString());
			}
		}
	}
}  

Wątek uruchamiam za pomocą metody execute() w funkcji obsługującej naciśnięcie przycisku. Problem polega na tym, że po wywołanu funkcji notifyAll() wątek nie powtarza już ani razu swojego przebiegu. wynikiem tego programu jest wypisanie tylko jednego A. (CheckAvalability() na razie zawsze zwraca true)

Jeżeli wywalę notifyAll(), to owszem działa ale strasznie spowalnia interfejs, nie mówiąc o tym, że nie potrafię przerwać tego wątku metodą cancel()

0
  1. W pętli powinno być isCancelled() a nie true. Nagłe wywalanie wątków jest "deprecated". Wątek sam powinien się dowiedzieć kiedy się zatrzymać.
  2. Zrób char[] z tej List<Character>(), a potem Stringa z tego char[].

Zapodaj więcej kodu, albo w ogóle kompletny test-case.

0

Wrzucam całą klasę Mainwindow. Dodam że poprawienie tej pętli nie pomogło. Dalej nie potrafię przerwać tego wątku. Mógłbyś tez przybliżyć dlaczego powinienem zmienić na char[]?

public class Mainwindow extends JFrame {

	private class Controler {
		private Character LastCharacter;
		
		public boolean CheckAvalability (Character c){
			LastCharacter = c;
			return true;
		}
	}
	
	private class LetterWriter extends SwingWorker<Object, Character> {
		Character Letter;
		
		public LetterWriter(Character let){
			Letter = let;
		}
		
		@Override
		protected Object doInBackground() throws Exception {
			while(!this.isCancelled()){
				if(Kontrolka.CheckAvalability(Letter)) {
					System.out.println(Letter.toString() + " Printing");
					publish(Letter);
				} else {
					System.out.println(Letter.toString() + " Waiting...");
					Kontrolka.wait();
				}
			}
			return null;
		}
		
		protected void process(List<Character> chunks){
			for(Character let : chunks){
				OutputTextArea.append(let.toString());
			}
		}
	}
	
	private static final long serialVersionUID = 1L;
	private JDesktopPane jDesktopPane = null;
	private JButton StartButton = null;
	private JButton StopButton = null;
	private JTextArea OutputTextArea = null;
	
	private Controler Kontrolka = null;
	private LetterWriter ThreadA = null;
	private LetterWriter ThreadB = null;

	private JDesktopPane getJDesktopPane() {
		if (jDesktopPane == null) {
			jDesktopPane = new JDesktopPane();
			jDesktopPane.setBackground(SystemColor.control);
			jDesktopPane.add(getStartButton(), null);
			jDesktopPane.add(getStopButton(), null);
			jDesktopPane.add(getOutputTextArea(), null);
		}
		return jDesktopPane;
	}

	private JButton getStartButton() {
		if (StartButton == null) {
			StartButton = new JButton();
			StartButton.setBounds(new Rectangle(202, 47, 63, 23));
			StartButton.setText("Start");
			StartButton.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					ThreadA.execute();
					ThreadB.execute();
					System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed()
				}
			});
		}
		return StartButton;
	}

	private JButton getStopButton() {
		if (StopButton == null) {
			StopButton = new JButton();
			StopButton.setBounds(new Rectangle(202, 98, 62, 26));
			StopButton.setText("Stop");
			StopButton.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					ThreadA.cancel(true);
					ThreadB.cancel(true);
					System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed()
				}
			});
		}
		return StopButton;
	}

	private JTextArea getOutputTextArea() {
		if (OutputTextArea == null) {
			OutputTextArea = new JTextArea();
			OutputTextArea.setBounds(new Rectangle(26, 23, 154, 124));
			OutputTextArea.setLineWrap(true);
			OutputTextArea.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
			OutputTextArea.setEditable(true);
			OutputTextArea.setEnabled(false);
			OutputTextArea.setBorder(BorderFactory.createLineBorder(Color.black));
		}
		return OutputTextArea;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				Mainwindow thisClass = new Mainwindow();
				thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				thisClass.setVisible(true);
			}
		});
	}

	public Mainwindow() {
		super();
		initialize();
		ThreadA = new LetterWriter('A');
		ThreadB = new LetterWriter('B');
		Kontrolka = new Controler();
	}

	private void initialize() {
		this.setSize(301, 208);
		this.setName("Main Window");
		this.setContentPane(getJDesktopPane());
		this.setTitle("Lab 3");
	}
}  
0
  1. Zamiana na String całości od razu ze względów wydajnościowych. Podejrzewam, że metoda append(String) jest dość kosztowna, więc lepiej wywoływać ją jak najmniej razy.

  2. Uruchomiłem ten kod u siebie (Ubuntu, OpenJDK) i raczej działa poprawnie. Przeładowałem metodę done() w SwingWorkerze i widzę że się wywołuje gdy kliknę przycisk Stop. Widzę także litery A i B na zmianę, tzn ciąg liter A, potem ciąg liter B, itd czyli tak jak powinno być.

  3. Użyj tutaj typu Void, a nie Object przy parametryzowaniu SwingWorker.

0

Klasę Cotroler zmieniłem na taką:

private class Controler {
		private Character LastCharacter;
		
		public boolean CheckAvalability (Character c){
			if (LastCharacter==c) {
				return false;
			} else {
				LastCharacter = c;
				return true;
			}
		}
	}

Chciałem zsynchronizować te dwa wątki żeby litery pojawiały się na zmianę. Niestety, pojawiają się losowo niekoniecznie w tej kolejności, co gorsza same przerywają po chwili...

Dodano:
Jak dopisałem notifyAll() po opublikowaniu litery, to wywala zawsze jedno AB, jednak nie zapętla się

0

Dodaj sobie taką metodę do SwingWorkera:

@Override
        protected void done() {

            try {
                get();
            } catch (InterruptedException ex) {
                System.out.println(ex);
            } catch (ExecutionException ex) {
                System.out.println(ex);
            }
            System.out.println("Done.");
        }

A potem zobacz co się dzieje.

0

Poprzerabiałem trochę, dodałem bloki synchronized. Wątki są już teoretycznie zsynchronizowane (w konsoli wywołują się odpowiednio), jednak do komponentu dalej litery wpadają losowo. Gdzie jeszcze powinienem to synchronizować? A może w ogóle zmienić podejście?

public class Mainwindow extends JFrame {

	private class Controler {
		private Character LastCharacter;
		
		public boolean CheckAvalability (Character c){
			if (LastCharacter==c) {
				return false;
			} else {
				LastCharacter = c;
				return true;
			}
		}
	}
	
	private class LetterWriter extends SwingWorker<Void, Character> {
		Character Letter;
		
		public LetterWriter(Character let){
			Letter = let;
		}
		
		@Override
		protected Void doInBackground() throws Exception {
			while(!this.isCancelled()){
				synchronized (Kontrolka) {
					if (Kontrolka.CheckAvalability(Letter)) {
						System.out.println(Letter.toString() + " Printing");
						publish(Letter);
						try {
							Kontrolka.notifyAll();
						} catch (Exception e) {
							System.out.println(e);
						}
					} else {
						System.out.println(Letter.toString() + " Waiting...");
						try {
							Kontrolka.wait();
						} catch (Exception e) {
							System.out.println(e);
						}
					}
				}
			}
			return null;
		}
		
		protected void process(List<Character> chunks){
			synchronized (OutputTextArea) {
				for (Character let : chunks) {
					OutputTextArea.append(let.toString());
				}
			}
		}
		

		@Override
		protected void done() {
			try {
				get();
			} catch (InterruptedException ex) {
				System.out.println(ex);
			} catch (ExecutionException ex) {
				System.out.println(ex);
			}
				System.out.println("Done.");
		}
	}
	
	private static final long serialVersionUID = 1L;
	private JDesktopPane jDesktopPane = null;
	private JButton StartButton = null;
	private JButton StopButton = null;
	private JTextArea OutputTextArea = null;
	
	private Controler Kontrolka = null;
	private LetterWriter ThreadA = null;
	private LetterWriter ThreadB = null;

	private JDesktopPane getJDesktopPane() {
		if (jDesktopPane == null) {
			jDesktopPane = new JDesktopPane();
			jDesktopPane.setBackground(SystemColor.control);
			jDesktopPane.add(getStartButton(), null);
			jDesktopPane.add(getStopButton(), null);
			jDesktopPane.add(getOutputTextArea(), null);
		}
		return jDesktopPane;
	}

	private JButton getStartButton() {
		if (StartButton == null) {
			StartButton = new JButton();
			StartButton.setBounds(new Rectangle(202, 47, 63, 23));
			StartButton.setText("Start");
			StartButton.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					ThreadA.execute();
					ThreadB.execute();
					System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed()
				}
			});
		}
		return StartButton;
	}

	private JButton getStopButton() {
		if (StopButton == null) {
			StopButton = new JButton();
			StopButton.setBounds(new Rectangle(202, 98, 62, 26));
			StopButton.setText("Stop");
			StopButton.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					ThreadA.cancel(true);
					ThreadB.cancel(true);
					System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed()
				}
			});
		}
		return StopButton;
	}

	private JTextArea getOutputTextArea() {
		if (OutputTextArea == null) {
			OutputTextArea = new JTextArea();
			OutputTextArea.setBounds(new Rectangle(26, 23, 154, 295));
			OutputTextArea.setLineWrap(true);
			OutputTextArea.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
			OutputTextArea.setEditable(true);
			OutputTextArea.setEnabled(false);
			OutputTextArea.setBorder(BorderFactory.createLineBorder(Color.black));
		}
		return OutputTextArea;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				Mainwindow thisClass = new Mainwindow();
				thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				thisClass.setVisible(true);
			}
		});
	}

	public Mainwindow() {
		super();
		initialize();
		ThreadA = new LetterWriter('A');
		ThreadB = new LetterWriter('B');
		Kontrolka = new Controler();
	}

	private void initialize() {
		this.setSize(301, 382);
		this.setName("Main Window");
		this.setContentPane(getJDesktopPane());
		this.setTitle("Lab 3");
	}
}
0

Metoda process(List<V>) jest wywoływana w EDT, więc tam nie musisz nic synchronizować. Zachowanie jest zgodne z oczekiwanym, gdyż wyniki metody publish są łączone w listę, jeżeli EDT jest zajęty i nie można wywołać process od razu.

W twoim przypadku chyba jednak dobrze by było zmienić podejście. Do głowy przyszedł mi następujący pomysł:

  1. Utworzyć normalne Thready, które generują litery.
  2. W środku tych Threadów tworzyć nowe Runnable dla każdej litery i wywoływać SwingUtilites.invokeLater() na nich.

Rozwiązanie to ma jednak pewną zasadniczą wadę. Jeżeli skolejkujesz za dużo zadań to interfejs się zawiesi. Nowe eventy lądują chyba na końcu listy, więc dowolny event, np kliknięcie, będzie musiał poczekać, aż wykonają się wszystkie zadania, które były w kolejce, gdy był do niej dodawany.

EDIT:
W sumie można skorzystać też z invokeAndWait() i wtedy raczej nie zaspamujemy tak bardzo kolejki do EDT.

1 użytkowników online, w tym zalogowanych: 0, gości: 1