0

Basically, Thread A wait()'s on a lock object, then Thread B notifyAll()'s. The function on thread A finishes, but doesn't return for normal operation.

It may be of note that I am using JavaFX. In order to keep my FX Thread separate from my work Threads, I make a new Thread in order to prevent issues when I lock it and such.

Here's my code (pared down to the functions in question):

public class ShortcutManager {

    private static final StringProperty STATE = new SimpleStringProperty("Create Shortcut");

    public static void create() {
        Thread creationThread = new Thread(() -> {
            // Do some work (sends logs to the logger)
            String result = prompt();
            if (result != null) {
                // Do more work (sends logs to the logger)
            } else {
                // Do other work (sends logs to the logger)
            }
        }
        creationThread.start();
    }

private static String prompt() {
    STATE.setValue("Waiting for user...");
    String result = popup.showAndWait();
    System.out.println("Result: \"" + result + "\"");
    return result;
}
public class CmlPopup implements Initializable {

    private boolean isClosed = false;

    public synchronized String showAndWait() {
        isClosed = false;
        Platform.runLater(() -> {
            stage.show();
        });
        while (!isClosed) {
            try {
                System.out.println("Waiting.");
                wait();
                System.out.println("Notified.");
            } catch (InterruptedException ex) {

            }
        }
        return getResult();
    }

    public synchronized void close() {
        stage.close();
        isClosed = true;
        System.out.println("Closing and notifying");
        notifyAll();
    }

}

Now - why am I locking it myself when the Stage has a showAndWait() function? Because that wasn't working either, plus it ends up pausing the FX thread instead of the creation thread, since both show() and showAndWait() must occur on the FX thread.

My output is as follows:

. . .

ShortcutManager State: Waiting for user...
Waiting.
Closing and notifying.
Notified.

And then nothing more. No printing of the result; no other work is done by the thread. My FX thread and other work threads operate fine, but this one seems to just terminate even though there is still more to be done.

P.S. Before you criticize me, I'm using Loggers where I intend to retain them, and System.out when I intend to remove them.

Bennett D
  • 61
  • 1
  • 1
  • 5
  • I'm not an expert in JavaFX, but your approach sounds very dangerous. The way you'd generally go about this is that you would create some other thread that did the work and when the thread is done, you'd tell the UI thread to repaint() (In Swing repaint() is thread safe). The paint() method would notice that your work was ready and do the appropriate UI stuff to make things pretty. – AminM Jun 20 '20 at 06:24
  • There are other approaches too. Like using a util.concurrent Queue of some sort. You have a thread add to that queue and the UI thread checks the queue. I think that approach is usually preferrable. Using wait() and notify() and synchronized is using too many low level primitives and its likely you'll have subtle bugs. Prefer constructs from util.concurrent. – AminM Jun 20 '20 at 06:24
  • 4
    Cannot reproduce the issue (of course I needed to change up some things to e.g. fix the syntax error to get the code to compile). Note that you can pretty easily retrieve information from the javafx application thread using `Platfrom.runLater` to retreive the data and then filling a `CompletableFuture` you created on the background thread with the info. See my post here for an example: https://stackoverflow.com/a/58761795/2991525 – fabian Jun 20 '20 at 12:44
  • 3
    It’s always better to use higher-level API for threading than to try to reproduce the same functionality yourself using threading primitives. The example provided by @fabian is a good example of this; also see https://stackoverflow.com/questions/14941084/javafx2-can-i-pause-a-background-task-service – James_D Jun 20 '20 at 20:46

1 Answers1

0

First - thank you to those helpful folks that commented. Although your solutions did not work for me, they pointed me in the right direction.

Now, my solution: I simply wrapped the popup result, blocking, etc in a CompletableFuture.

public class CmlPopup<R, D> implements Initializable {
    
    private final Object closeLock = new Object();
    private volatile boolean isClosed = false;

    ----

    public CompletableFuture<R> showAndGet() {
        Platform.runLater(() -> {
            stage.show();
        });
        return CompletableFuture.supplyAsync(() -> {
            synchronized (closeLock) {
                while (!isClosed) {
                    try {
                        closeLock.wait();
                    } catch (InterruptedException ex) {
                        
                    }
                }
                return contentManager.getResult();
            }
        }

    public void showThenRun(BiConsumer<? super R, ? super Throwable> action) {
        CompletableFuture.supplyAsync(() -> {
            synchronized (closeLock) {
                while (!isClosed) {
                    try {
                        closeLock.wait();
                    } catch (InterruptedException ex) {
                        
                    }
                }
                return contentManager.getResult();
            }
        }).whenComplete(action);
        Platform.runLater(() -> {
            stage.show();
        });
    }

    public void close() {
        stage.close();
        synchronized (closeLock) {
            isClosed = true;
            closeLock.notifyAll();
        }
    }

}
public class ShortcutManager {

    private static final CmlPopup<String, Integer> POPUP = new CmlPopup(...);

    ----

    private static void prompt(File userData) {
        POPUP.showThenRun((String result, Throwable ex) -> {
            System.out.println("Result: \"" + result+ "\"");
            ShortcutManager.identifyAndGridImages(result, userData);
        });
    }
    
    ----
    
}

This works perfectly reliably. For whatever reason, having a CompletableFuture for the object itself never runs the whenComplete function even the the future is supposedly "complete."

Bennett D
  • 61
  • 1
  • 1
  • 5