JavaFX Alert Confirmation Dialog aus einem Service Thread

ralfb1105

Bekanntes Mitglied
Hallo zusammen,

ich habe schon wieder eine Frage/Problematik wo ich nicht so recht weiter komme und hoffe Ihr könnt mir wieder mal helfen.
Hier kurz die Beschreibung meiner Thematik die ich lösen möchte:
Ich habe einen Service Thread in dem ich verschiedene Tasks durchführe, wie z.B:
  • Task 1: Check if ojdbc driver is available.
  • Task 2: Try database connection. Printout Java and JDBC Version
  • Task 3: Check the database version
Nun hat der User noch die Möglichkeit, bei Bedarf einen "normalen" DB User anzugeben, und wenn der gesetzt ist, soll überprüft werden ob es den User in der DB schon gibt. Falls es den User noch nicht gibt, soll ein Confirmation Dialog angezeigt werden der den User zur Bestätigung zum erstellen des User auffordert.

Den Confirmation Dialog habe folgendermassen codiert:
Java:
private void showConfirmationDialogUserCreation() {
        Alert confirmUserCreation = new Alert(AlertType.CONFIRMATION);
        confirmUserCreation.setTitle("Create User");
        confirmUserCreation
                .setHeaderText("User doesn't exist.\nDo you want to create the user with the given credentials?");
        // confirmUserCreation.setContentText("Create User hr");

        // Create new Buttons
        ButtonType createUser = new ButtonType("Create User");
        ButtonType cancel = new ButtonType("Cancel");

        // Remove default ButtonTypes
        confirmUserCreation.getButtonTypes().clear();
       
        // Set new defined Buttons
        confirmUserCreation.getButtonTypes().addAll(createUser, cancel);

        Optional<ButtonType> option = confirmUserCreation.showAndWait();

        if (option.get() == null) {
            LOGGER.severe("Entered User Confirmation Dialog: No selection!");
        } else if (option.get() == createUser) {
            System.out.println("User hr created");
        } else if (option.get() == cancel) {
            System.out.println("User declined to create User hr");
        } else {
            LOGGER.severe("Entered User Confirmation Dialog case where we shouldn't be ...");
        }

    }
Im Service Thread sieht der Abschnitt dann folgendermaßen aus:
Java:
// Worker Thread called when DB Connect Button tapped ...
    @SuppressWarnings("rawtypes")
    Service serviceWorkerConnectDB = new Service() {
        String dbBanner;

        @Override
        protected Task createTask() {
            return new Task() {
                @Override
                protected Void call() throws Exception {
                    // Start enetering execution code here ...
...
...
// Task 4: If Username entered try connection as normal user ...
                    if (!dbUserName.getText().isEmpty()) {
                        updateMessage("Task4: Check if User " + dbUserName.getText() + " exists.");
                        LOGGER.info("Task4: Check if User " + dbUserName.getText() + " exists.");
                        String queryCheckIfUserExist = "select USERNAME from dba_users where USERNAME='" + dbUserName.getText().toUpperCase() + "'";
                        Statement stmtCheckIfUserExist = connSys.createStatement();
                        ResultSet rsetCheckUser = stmtCheckIfUserExist.executeQuery(queryCheckIfUserExist);
                        if (rsetCheckUser.next()) {
                            String user = rsetCheckUser.getString("USERNAME");
                            if (user.equalsIgnoreCase(dbUserName.getText())) {
                                updateMessage("Task4: Check if User " + dbUserName.getText() + " exists: Success!");
                                LOGGER.info("Task4: Check if User " + dbUserName.getText() + " exists: Success!");   
                            }
                        } else {
                            updateMessage("Task4: Check if User " + dbUserName.getText() + " exists: User does not exist.");
                            LOGGER.info("Task4: Check if User " + dbUserName.getText() + " exists: User does not exist!");
                                showConfirmationDialogUserCreation();   
                        }
                    }
                   

                    // End handling ...
                    updateProgress(1.0, 1.0);
                    connectDB.setDisable(false);
                    dbPatchInfo.setDisable(false);
                    connSys.close();
                    // End entering execution code here ...
                    return null;
                }

Wenn ich das programm starte und es den eingegeben User nicht gibt, dann endet das Programm nicht aber der Confirmation Dialog wird nicht angezeigt.

Wenn ich das mit einem
Java:
Platform.runLater(() -> {
                                showConfirmationDialogUserCreation();
                            });
mache, dann wird der Dialog angezeigt, ABER das programm, sprich der Service Task läuft natürlich weiter, was ja auch nicht so gewollt ist.

Frage:

Wie kann ich in dem Service Task den Confirmation Dialog anzeigen so dass der Servcie Task an der Stelle auf die User Bestätigung wartet und ich dann auch das Ergebniss (Create User oder Cancel) im weiteren Verlauf des Servcie Task weiter verabeiten kann --> User in der DB erstellen order im Programm ohne Erstellung eines User weiter machen?

Gruß

Ralf
 
Hallo zusammen,

da es bis jetzt keine Antworten auf meine Frage gibt vermute ich mal das ich das Problem bzw. die Fragestellung nicht konkret genug gestellt habe. Falls jemand eine Frage zu meiner Darstellung hat oder auch nur eine Idee - einfach melden - bin für jeden Hinweis dankbar.

Gruß
Ralf
 
Mal eine Idee:
Java:
import java.util.Optional;
import java.util.concurrent.atomic.*;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.concurrent.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import javafx.stage.*;

public class Background extends Application {
    private StringProperty status = new SimpleStringProperty("NOT RUNNING");
    private Task<Void> task = new Task<Void>() {
        @Override
        protected Void call() throws Exception {
            Platform.runLater(() -> status.set("Step 1"));
            Thread.sleep(1000);
            Platform.runLater(() -> status.set("Step 2"));
            Thread.sleep(1000);
            AtomicInteger value = new AtomicInteger();
            synchronized(value) {
                Platform.runLater(() -> getInput(value));
                value.wait();
            }
            Platform.runLater(() -> status.set("Step 3"));
            Thread.sleep(1000);
            return null;
        }
    };

    private void getInput(AtomicInteger result) {
        Alert confirmUserCreation = new Alert(AlertType.CONFIRMATION);
        confirmUserCreation.setTitle("Create User");
        confirmUserCreation
                .setHeaderText("User doesn't exist.\nDo you want to create the user with the given credentials?");
        // confirmUserCreation.setContentText("Create User hr");

        // Create new Buttons
        ButtonType createUser = new ButtonType("Create User");
        ButtonType cancel = new ButtonType("Cancel");

        // Remove default ButtonTypes
        confirmUserCreation.getButtonTypes().clear();
       
        // Set new defined Buttons
        confirmUserCreation.getButtonTypes().addAll(createUser, cancel);

        Optional<ButtonType> option = confirmUserCreation.showAndWait();
        int r = 0;

        if (option.get() == null) {
        } else if (option.get() == createUser) {
            r = 1;
            System.out.println("User hr created");
        } else if (option.get() == cancel) {
            r = 2;
            System.out.println("User declined to create User hr");
        } else {
            r = 3;
        }
        synchronized(result) {
            result.set(r);
            result.notify();
        }
    }


    public void start(Stage s) {
        Label lbl = new Label();
        lbl.textProperty().bind(status);
        Scene scene = new Scene(lbl);
        s.setScene(scene);
        s.show();

        new Thread(task).start();
    }
}
 
Hallo mihe7,

DANKE! Deine Idee funktioniert bei mir auf den ersten Blick/Test super 😀.
Ich habe dazu noch folgende Frage, damit ich auch wieder etwas von dem verstehe was Ihr mir alles beibringt ...
1. Warum muss ich hier als Integer den AtomicInteger nehmen ? Thread sicher? Methoden ?
2. Die .wait(), .set() und .notify() Methoden .. die wait und notify beziehen sich auf den Thread und sie set() auf den AtomicInteger?!

Gruß
Ralf
 
damit ich auch wieder etwas von dem verstehe
Das hatte ich gestern ganz überlesen. Ein paar Zusatzinformationen zum Monitor in Java findest Du unter https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

Zur Erklärung des Codes: mit synchronized(o) verschafft man sich exklusiven Zugriff auf den Monitor des Objekts o (sollte der belegt sein, muss man ggf. warten, bis er wieder frei ist). Bei o.wait(); passieren zwei Dinge: man legt den aktuellen Thread schlafen, nachdem er in die Warteschlange des Monitors von o eingereiht wurde. Bei o.notify(); wird ein Thread aus der Warteschlange des Monitors von o aufgeweckt.

Etwas konkreter auf den Code bezogen: der Task nimmt ein AtomicInteger-Objekt (dazu gleich mehr), verschafft sich exklusiven Zugriff auf dessen Monitor, übergibt das Objekt an eine Methode, die "später" im UI-Thread ausgeführt wird und wartet nun auf Benachrichtigung vom Monitor; dabei wird der Monitor freigegeben.

Die im UI-Thread aufgerufene Methode verschafft sich ebenfalls exklusiven Zugriff auf den Monitor des übergebenen Objekts (das AtomicInteger-Objekt von eben). Damit ist nebenbei sichergestellt, dass entweder der Task oder der UI-Thread auf das AtomicInteger-Objekt zugreifen, niemals aber beide gleichzeitig. Via notify() wird dem Monitor mitgeteilt, dass es einen wartenden Thread (Deinen Task) aufwecken kann.

Für diesen Zweck kannst Du jedes x-beliebige Objekt verwenden - es muss in beiden Threads nur das selbe(!) Objekt sein.

Der Code oben ist noch nicht ganz vollständig, da tatsächlich auf eine Bedingung gewartet wird, die nicht abgeprüft wird. Die Bedingung könnte man salopp so formulieren: "Task wurde von der aufgerufenen Methode aufgeweckt". Hintergrund ist, dass es sog. spurious wakeups gibt, d. h. Threads werden aufgeweckt, obwohl gar kein notify() aufgerufen wurde. Deswegen müsste das wait oben noch in eine Schleife gepackt werden, die eine entsprechende Bedingung überprüft.

Kommen wir zum AtomicInteger: das ist tatsächlich nur dazu da, irgendeinen Rückgabewert speichern zu können. Alternativ könntest Du z. B. auch den Dialog in eine Klasse auslagern und den Rückgabewert in einer Instanzvariablen speichern. Dann erzeugst Du im Background-Thread ein Objekt von der Dialog-Klasse und arbeitest mit diesem. Du kannst das notify() auch über einen Listener bzw. JavaFX Callback erledigen usw. Möglichkeiten gibt es genügend - auch andere (aber ähnliche) Ansätze.

Ein ganz anderer Ansatz wäre z. B. die Tasks aufzudröseln und dann per Listener im UI-Thread auf Beendigung zu reagieren:

Task 1 -> fertig -> starte Task 2
Task 2 -> fertig -> starte Task 3
Task 3 -> fertig -> Task 4 ist dann eigentlich kein Background-Task sondern einfach der Dialog-Aufruf, im Anschluss ginge es dann weiter mit dem Starten des nächsten Tasks.
 
Hallo mihe7,

vielen Dank für Deine ausführlichen und sehr verständlichen Erklärungen!!!
Ich habe noch eine Frage zu
Der Code oben ist noch nicht ganz vollständig, da tatsächlich auf eine Bedingung gewartet wird, die nicht abgeprüft wird. Die Bedingung könnte man salopp so formulieren: "Task wurde von der aufgerufenen Methode aufgeweckt". Hintergrund ist, dass es sog. spurious wakeups gibt, d. h. Threads werden aufgeweckt, obwohl gar kein notify() aufgerufen wurde. Deswegen müsste das wait oben noch in eine Schleife gepackt werden, die eine entsprechende Bedingung überprüft.
Ich vermute es geht um folgenden Code:
Code:
AtomicInteger value = new AtomicInteger();
                            synchronized(value) {
                                Platform.runLater(() -> getInputConfirmationDialogCreateUser(value));
                                value.wait();
                            }
und hier im speziellen um "value.wait();" - richtig?

Mir ist nicht ganz klar wie ich nun überprüfe ob es der "echte" notify() war oder irgendein "spurious wakepup". Ich habe keine Methode gefunden die ich jetzt so in einer Abfrage verwenden könnte um zu entscheiden ob der notify() des AtomicInteger Objekts gekommen ist.

Gruß
Ralf
 
und hier im speziellen um "value.wait();" - richtig?
Ja.
Ich habe keine Methode gefunden die ich jetzt so in einer Abfrage verwenden könnte um zu entscheiden ob der notify() des AtomicInteger Objekts gekommen ist.
Das funktioniert über den Rückgabewert. Wenn die Methode z. B. Werte ab 0 zurückgibt, dann setzt Du den Wert vorher halt auf einen negativen Wert:
Java:
AtomicInteger value = new AtomicInteger();
value.set(-1);
while (value.get() == -1) {
    synchronized(value) {
        Platform.runLater(() -> getInputConfirmationDialogCreateUser(value));
        value.wait();
    }
}
 
Hallo mihe7,

nochmals Danke! Ich muss zugeben das ich darauf nicht gekommen wäre, auch wenn es so einleuchtend ist wenn ich es jetzt lese. Ich werde das Morgen gleich mal testen.

Gruß
Ralf
 

Neue Themen


Zurück
Oben