Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting return values from SwingUtilities.InvokeLater calls?

Tags:

java

swing

Within my java application I have a JTabbedPane on a main JFrame with many JPanel tabs. Within the tabs I have a function

public void newStatus(final String arg)
{
    Runnable sendMsg = new Runnable() 
    {
        @Override
        public void run() 
        {
           mainView.newStatusLine(arg);
        }
    };
    SwingUtilities.invokeLater(sendMsg);
}

this function calls the main JFrame mainView function to write some text to a JTextPane. My issue is that this doesn't allow me to get a return value from the main JFrame. I would like to do something like

public InfoObj getInfo()
{
    Runnable sendMsg = new Runnable() 
    {
        @Override
        public void run() 
        {
           return mainView.getInfo();
        }
    };
    SwingUtilities.invokeLater(sendMsg);
}

But I am not sure how to make this work. I have tried to and followed my IDE's messages to see if I can get it to maybe work, but I cannot override Runnable.Run to return something.

Are there any mechanisms to do something like this?

EDIT: For HoverCraftFullOfEels, The overall problem is talking between JPanels themselves and between the main JFrame and a JPanel. At some point a JPanel wants to tell the main JFrame to do something, or it might want to get some data from it. But from everything I know I cannot just pass a reference of this to either the JFrame or JPanel and use it to call a public function or read some public field in either.

Sometimes I want to do this not on the EDT via a spawned thread. Some of the JPanel spawn threads and I would like to pass the JPanels reference to the main JFrame so it can call functions within it to tell the user something about what the thread is doing.

like image 512
KDecker Avatar asked Sep 03 '14 02:09

KDecker


People also ask

What is SwingUtilities invokeLater () used for?

An invokeLater() method is a static method of the SwingUtilities class and it can be used to perform a task asynchronously in the AWT Event dispatcher thread. The SwingUtilities. invokeLater() method works like SwingUtilities. invokeAndWait() except that it puts the request on the event queue and returns immediately.

What is the difference between invokeAndWait and invokeLater?

Their only difference is indicated by their names: invokeLater simply schedules the task and returns; invokeAndWait waits for the task to finish before returning. You can see examples of this throughout the Swing tutorial: SwingUtilities. invokeLater(new Runnable() { public void run() { createAndShowGUI(); } });

What is SwingUtilities invokeLater new runnable ()?

SwingUtilities class has two useful function to help with GUI rendering task: 1) invokeLater(Runnable):Causes doRun. run() to be executed asynchronously on the AWT event dispatching thread(EDT). This will happen after all pending AWT events have been processed, as is described above.

What is Java SwingUtilities?

Stores the position and size of the inner painting area of the specified component in r and returns r .


2 Answers

I know this question is quite old, but the following should provide a handy workaround for the issue.

You'll need this class to be able to define a final object which encapsulates your return value:

public class Shell<T> {

    private T value;

    public T getValue() {
        return this.value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

Afterward, you can run the whole thing is a nested Runnable f.e. like this:

public InfoObj getInformation() {
    final Shell<InfoObj> returnObj = new Shell<InfoObj>();
    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                returnObj.setValue(mainView.getInfo());
            }
        });
    } catch (InvocationTargetException | InterruptedException e1) {
        e1.printStackTrace();
    }
    return returnVal.getValue();
}

Note that I changed the method name from the question because this method is not meant to be called from within the Runnable. You can call this method from any thread and it will return you a value. However, I've also changed the invokeLater() to invokeAndWait(), because it can happen that the return value is null if the operation you execute in the Runnable has not assigned a value to the Shell object yet. If you don't want your thread to be blocked and use invokeLater() you can also return the Shell object to read it when it has a value.

like image 27
User1337 Avatar answered Sep 20 '22 12:09

User1337


It looks like your application data is all bound up inside your user interface objects. If everything you do can be done on the EDT, then you should be OK. You can make direct calls among the objects that need information from each other. Since all of this is on the EDT, it's effectively single-threaded, and there are no race conditions or deadlocks. This, however, can couple various parts of the UI code to each other rather too tightly. In that case you can use some kind of observer or listener pattern as the commenters have suggested. This works fine in a single-threaded, event-driven environment like with AWT/Swing.

But then you say you might want to do this from some thread other than the EDT. That changes everything.

(As an aside, this is the sort of thing that causes people to consider having all your application data bound up inside your UI objects to be an anti-pattern. It makes it quite difficult to get to this data from outside the UI subsystem.)

You could add an observer pattern, but there are restrictions. The UI owns the data, so only the EDT can change the data and fire events to observers. The observers lists need to maintained in a thread-safe fashion. Presumably the observers will want to update data structures in response to events. These data structures will have to be thread-safe, since they're accessed both from the EDT and from your other thread. This approach will work, but you have to do a lot of thinking about which thread is calling which methods, and which data structures have to be made thread-safe.

Assuming you don't want to go this route, let's return to your original question, which was about how to return something from invokeLater. Doing this would let you keep your data in the UI while allowing other threads to get data out of the UI when they need it. It is possible to do this, with a bit of indirection. It does have risks, though.

Here's the code. This can be called from the "other" (non-EDT) thread.

InfoObj getInfo() {
    RunnableFuture<InfoObj> rf = new FutureTask<>(() -> getInfoObjOnEDT());
    SwingUtilities.invokeLater(rf);
    try {
        return rf.get();
    } catch (InterruptedException|ExecutionException ex) {
        ex.printStackTrace(); // really, you can do better than this
    }
}

The trick here is that RunnableFuture is an interface that is both a Runnable and a Future. This is useful because invokeLater takes only a Runnable, but Future can return a value. More specifically, Future can capture a value returned in one thread and store it until another thread wants to get it. FutureTask is a convenient, ready-made implementation of RunnableFuture that takes a Callable argument, a function that can return a value.

Basically we create a FutureTask and hand it a bit of code (the Callable, a lambda expression in this example) that will run on the EDT. This will gather the information we want and return it. We then post this to the EDT using invokeLater. When the other thread wants the data, it can call get() immediately or it can hang onto the Future and call get() later. There is a small inconvenience in that Future.get() throws a couple checked exceptions that you have to deal with.

How does this work? If the EDT runs the Callable first, the return value is stored in the Future until the other thread calls get() on it. If the other thread calls get() first, it's blocked until the value becomes available.

And there's the rub. Astute readers will recognize that this has the same risk as invokeAndWait(), namely, if you're not careful, you can deadlock the system. This can occur because the thread that calls get() might block waiting for the EDT to process the event posted by invokeLater. If the EDT is blocked for some reason -- perhaps waiting for something held by the other thread -- the result is deadlock. The way to avoid this is to be extremely careful when calling something that might block waiting for the EDT. A good general rule is not to hold any locks while calling one of these blocking methods.

For an example of how you can get yourself into trouble with invokeAndWait or with invokeLater(FutureTask), see this question and my answer to it.

If your other thread is entirely decoupled from the UI data structures, this technique should be quite effective.

like image 134
Stuart Marks Avatar answered Sep 19 '22 12:09

Stuart Marks