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

[Java Swing] Komunikacja między wątkami i komponentami Swing
sprzedamsanki
  • Rejestracja:prawie 18 lat
  • Ostatnio:około 8 lat
  • Postów:278
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?


The most exciting phrase to hear in science, the one that heralds new discoveries, is not 'Eureka!' (I found it!) but 'That's funny'
Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:dzień
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.


"Nie popełnia błędów tylko ten, kto nic nie robi"
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.

Kopiuj
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

Kopiuj
Thread t = new Thread(new ObsługaPrzyciskow());
t.start();
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:10 minut
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ć.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
sprzedamsanki
  • Rejestracja:prawie 18 lat
  • Ostatnio:około 8 lat
  • Postów:278
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.


The most exciting phrase to hear in science, the one that heralds new discoveries, is not 'Eureka!' (I found it!) but 'That's funny'
edytowany 1x, ostatnio: sprzedamsanki
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:10 minut
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ć.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
sprzedamsanki
  • Rejestracja:prawie 18 lat
  • Ostatnio:około 8 lat
  • Postów:278
0

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

Kopiuj
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()


The most exciting phrase to hear in science, the one that heralds new discoveries, is not 'Eureka!' (I found it!) but 'That's funny'
edytowany 3x, ostatnio: sprzedamsanki
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:10 minut
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
sprzedamsanki
  • Rejestracja:prawie 18 lat
  • Ostatnio:około 8 lat
  • Postów:278
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[]?

Kopiuj
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");
	}
}  

The most exciting phrase to hear in science, the one that heralds new discoveries, is not 'Eureka!' (I found it!) but 'That's funny'
edytowany 4x, ostatnio: sprzedamsanki
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:10 minut
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
sprzedamsanki
  • Rejestracja:prawie 18 lat
  • Ostatnio:około 8 lat
  • Postów:278
0

Klasę Cotroler zmieniłem na taką:

Kopiuj
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ę


The most exciting phrase to hear in science, the one that heralds new discoveries, is not 'Eureka!' (I found it!) but 'That's funny'
edytowany 3x, ostatnio: sprzedamsanki
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:10 minut
0

Dodaj sobie taką metodę do SwingWorkera:

Kopiuj
@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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
sprzedamsanki
  • Rejestracja:prawie 18 lat
  • Ostatnio:około 8 lat
  • Postów:278
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?

Kopiuj
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");
	}
}

The most exciting phrase to hear in science, the one that heralds new discoveries, is not 'Eureka!' (I found it!) but 'That's funny'
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:10 minut
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 3x, ostatnio: Wibowit

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.