EDT und Nebenläufigkeit

Moin,

ich habe in einem nebenläufigen Thread eine lesende I/O-Operation, die mit der Einblendung einer Information in einem JWindow (die über der GUI liegt) begonnen wird.
Nach Abschluss der Operation wird die Einblendung geschlossen und die aufbereiteten Daten an die GUI übergeben.

Was davon muss mit dem EDT synchroniert werden?
Muss das innerhalb der run()-Methode des Threads per Aufruf von SwingUtilities.invokeLater(new Runnable() { ...}); gemacht werden?

Ich komme hier jedesmal ins stolpern.

Nur der Teil, der die View ändert muss innerhalb des Runnables bemacht werden den Du an
SwingUtilities.invokeLater(new Runnable() { ...}); übergibst.

bye
TT

Hey Lex,
ein mögliches Grund-Konstruktu könnte in etwa so aussehen :

{
	(new Thread(new Runnable()
	{
		public void run()
		{
			// Verarbeitung des Events
			SwingUtilities.invokeLater(new Runnable()
			{
				public void run()
				{
					// Änderungen an der GUI durch wiedereinkoppeln in den EDT
				}
			});
		}
	})).start();
}```
Alle Änderungen der GUI müssten also korrekterweise in das innerste Runnable damit sie korrekt in den EDT eingekoppelt und durch diesen ausgeführt werden.
Soweit ich jetzt verstanden habe soll vor der I/O-op noch was angezeigt werden, dazu würde sich dann invokeAndWait() eignen da so sichergestellt wird das halt erst die GUI-Änderung komplett ausgeführt und dann erst mit der Verarbeitung des Events (in deinem Fall die I/O-op) fortgefahren wird.
Für die abschließende Darstellung der Daten macht es eigentlich keinen Unterschied ob invokeLater() oder invokeAndWait() verwendet wird da es eh am Ende des Verarbeitungs-Threads gecallt wird.

Sicher gibt es noch bessere Varianten z.B. über SwingWorker für Statusaktualisierungen wie einer JProgressBar, aber der obige Code sollte soweit für "einfache" Event-Behandlung ausreichen.

Für solche Aufgaben kann man übrigens auch den SwingWorker gut nutzen.

Diesen Hinweis hatte ich ja bereits gegeben, vielleicht wäre es für Lex und uns andere hilfreich wenn du auch ein Beispiel dazu geben könntest wie man sowas umsetzen kann.

Ist das „EIN“ Ergebnis, oder Dinge, die stückweise an das GUI übergeben werden könnten? (Der SwingWorker hat genau dafür die publish/process-Methoden…)

Danke euch, für die Hilfe! :slight_smile:
Im Prinzip kann man das schon als ein Ergebnis sehen, weil das Ganze in einem anderen Thread passiert, auch die eingelesenen Daten, werden in diesem Thread in einer eigenen Methode aufbereitet.
Das Ergebnis wird, hier in diesem Fall einer JTable, zu Darstellung übergeben. Anschließend wird die Information (eigentlich nur sowas wie „JTable wird aktualisiert…“) wieder geschlossen.

Sind diese Einblendungen in diesem Fall überhaupt eine Änderung an der GUI?
@Sen-Mithrarin : Im Byte-Welt-Wiki kann man ein Beispiel finden.

[quote=L-ectron-X]Sind diese Einblendungen in diesem Fall überhaupt eine Änderung an der GUI?[/quote]Ja.

bye
TT

Danke euch, ich denke, ich bin nun wieder auf den Pfad gebracht worden. :wink:

Tatsächlich hatte ich vor kurzem sowas gebastelt, wo (ganz grob) ein SwingWorker aus einer Liste von Datenelementen Tabellenzeilen für eine JTable erstellt - und zwar im Hintergrund. Der Code sah (angepasst und einige spezifische Teile entfernt, aber) GROB so aus…

static void fillTable(
  final JTable table, final DefaultTableModel tableModel,
  final List<DataItem> dataItems)
{
  table.setEnabled(false);
  SwingWorker<Void, Object[]> tableFillingWorker = 
      new SwingWorker<Void, Object[]>()
  {
      @Override
      protected Void doInBackground() throws Exception
      {
          for (DataItem dataItem : dataItems)
          {
              Object rowData[] = createTableRowData(dataItem);
              publish(rowData);
          }
          return null;
      }
      
      @Override
      protected void process(List<Object[]> chunks)
      {
          for (Object[] rowData : chunks)
          {
              tableModel.addRow(rowData);
          }
      }
      
      @Override
      protected void done()
      {
          table.setEnabled(true);
      }
  };
  tableFillingWorker.execute();
}

Danach habe ich aber gemerkt, dass das Unfug ist: Es ist in vieler Hinsicht besser, diese Daten direkt über eine Implementierung von „AbstractTableModel“ an die Tabelle zu übergeben, statt sie aufwändig in ein neues DefaultTableModel zu kopieren/konvertieren. Inwieweit das bei dir möglich ist, weiß man natürlich erstmal nicht…

EDIT: Dieses Mention-Problem muss SO SCHNELL WIE MÖGLICH behoben werden. Nochmal: Notfalls abschalten, bis eine bessere Lösung gefunden ist. So geht ja der ganze gepostete Code kaputt :frowning:

Hi Marco13,

hier mal der Code, nach diesem Thema:

    public void updateTable() {
        if (efProxy != null && !isUpdating) {
            isUpdating = true; //so lange Update läuft, kein weiterer Updateversuch möglich

            new Thread(new Runnable() {
                JWindow window = null;

                public void run() {
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            window = getWaitDisplay("Tabelle wird aktualisiert...");
                            window.setVisible(true);
                        }
                    });

                    try {
                        efProxy.update(); // Daten auslesen
                    } catch (IOException ex) {
                        System.err.println(ex);
                        SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                                window.setVisible(false);
                                window.dispose();
                            }
                        });
                        JOptionPane.showMessageDialog(MainPanel.this, ex.getMessage(), "java.io.IOException", JOptionPane.ERROR_MESSAGE);
                        Thread.currentThread().interrupt(); //wird das Setzen des Flags auch den Thread abbrechen?
                        return;
                    }

                    createTableData(); //Datenmodel zusammenbauen und übergeben

                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            window.setVisible(false);
                            window.dispose();
                        }
                    });

                    isUpdating = false;
                }
            }).start();
        }
    }```

Sieht irgendwie nicht "richtig" aus. Ich denke auch, dass der Thread im Fehlerfall nicht abgebrochen wird. Da habe ich auch keine Ahnung, wie das gehen soll.

Ich kann mich mit folgenden Anmerkungen auch irren, daher bitte mit Vorsicht zu genießen …

  1. Ich würde hier eher invokeAndWait() nutzen da man so sicher gehen kann das erst die GUI-Aktion fertig ausgeführt und dann fortgefahren wird. Wäre zumindest in soweit logisch als das man argumentieren könnte : erstmal die Info anzeigen und dann erst weiter machen. Aber das ist natürlich dir überlassen, nur so n kleiner Ansatz.

  2. Auch wenn das showMesssageDialog() bestimmt auch so funktioniert würde ich es auf Grund der Referenz auf deine Haupt-Component mit in das invokeXXX() packen. Bin mir dabei aber nicht sicher.

  3. Ein setVisible(false) vor einem dispose() ist unnötig da bei einem dispose() der Peer “vernichtet” wird und somit das Fenster nicht “nur” unsichtbar wird sondern komplett entfernt. Ist aber eher Mikrooptimierung.

  4. Da nach dem Thread.interrupt() ein return; folgt wird run() eh beendet und der Thread läuft in TERMINATED, Zeile 32 sollte also im Fehlerfall nicht erreicht werden. Von daher wird der Thread eher weniger “abgebrochen” als durch das return; normal “zu Ende” gebracht.

  5. Wichtig wäre was in “createTableData()” passiert und ob dort das TableModel geändert wird. Falls ja : gehört auch in den invokeXXX() call da eine Änderung des TableModel automatisch ein fireChangedEvent() auslöst was die GUI aktualisiert was wie wir wissen nur im EDT passieren sollte.

Alles in allem denke ich soweit ich das Wiki-Beispiel mit SwingWorker verstanden habe (wobei auch das dringen überarbeitet werden sollte) angebrachter wäre da dort die entsprechenden Aktionen in den korrekten Threads ablaufen was bei dir alles irgendwie mehr oder weniger quergewuselt aussieht.

Wie gesagt : sind jetzt eher so Dinge die beim drübergucken auffallen, und kann auch sein das hier und da was nich ganz so stimmt wie ichs geschrieben habe, aber man sollte es sich doch mal ansehen, auch wenn es letztlich vermutlich doch nur Mikrooptimierungen sein werden.

Das „window“ ist wohl ein modaler Dialog? In http://docs.oracle.com/javase/8/docs/api/javax/swing/SwingWorker.html#get-- ist ein Beispiel, wie das mit SwingWorker gehen würde. Wenn man das händisch nachbauen will, ist es etwas komplizierter. Beim schnellen drüberschauen sieht das grundsätzlich nicht falsch aus, aber … ich stimme zu, dass es auch nicht „richtig“ aussieht :wink: in dem Sinne dass es kompliziert wirkt, und man meinen sollte, dass es einfacher gehen sollte. Aber ich schätze, dass das schon durch das Aufteilen in ein paar Methoden abgeschwächt werden könnte

class SwingTaskTest
{
	public void updateTable() {
		if (efProxy != null && !isUpdating) {
			isUpdating = true; // so lange Update läuft, kein weiterer Updateversuch möglich

			new Thread(new Runnable() {
				public void run() {
					updateTableInBackground();
				}
			}).start();
		}
	}


	private void updateTableInBackground() {
		createAndShowInfoWindow();

		try {
			efProxy.update(); // Daten auslesen
		} catch (IOException ex) {
			System.err.println(ex);
			hideAndDisposeInfoWindow();
			JOptionPane.showMessageDialog(null, ex.getMessage(), "java.io.IOException", JOptionPane.ERROR_MESSAGE);
			Thread.currentThread().interrupt(); // wird das Setzen des Flags auch den Thread abbrechen?
			return;
		}
		createTableData(); // Datenmodel zusammenbauen und übergeben
		hideAndDisposeInfoWindow();
		isUpdating = false;
	}

	private void hideAndDisposeInfoWindow() {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				window.setVisible(false);
				window.dispose();
			}
		});
	}

	private void createAndShowInfoWindow() {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				window = getWaitDisplay("Tabelle wird aktualisiert...");
				window.setVisible(true);
			}
		});
	}
    
	// Dummy stuff...
	JWindow window = null;
	static class EfProxy {
		void update() throws IOException {
		}
	};

	private EfProxy efProxy = null;
	boolean isUpdating = false;

	private JWindow getWaitDisplay(String string) {
		return null;
	}

	private void createTableData() {
	}
	
}

Aaaaber: Das war wirklich nur ein erster Blick, und man müßte das nochmal kritisch(st) durchgehen. Insbesondere scheint es so, als ob z.B. im Fehlerfall „isUpdating“ immer auf „true“ bleibt, was vermutlich nicht gewünscht ist…

Nein ein JWindow, das setAlwaysOnTop bekommen hat.

:o Oh Schreck, das habe ich total außer Acht gelassen…

Mein Gefühl sagte mir die ganze Zeit, dass was an der Sache Mist ist. Mangels Erfahrung, war ich nicht in der Lage, es anders zu schreiben.

Danke Marco13, danke Sen-Mithrarin, ich werde das Codestück noch mal durchackern.

Da hat Marco mal wieder gut aufgepasst (ich überles solche Kleinigkeiten irgendwie immer).
Was mir noch zusätzlich auffällt : ist die IOException wirklich nötig oder würde es auch mit einem boolean als return-type der update()-Methode reichen ? Denn so sieht es irgendwie nach “Programmflusssteuerung durch Exceptions” aus, was ja auch eher sub-optimal ist.