InvokeLater ordering with events
Asked Answered
P

2

9

The EventQueue javadoc states that it is required that enqueued events are dispatched sequentially and in order.

Is it correct that Runnables enqueued with EventQueue.invokeLater are guaranteed to be dispatched before subsequent user events (e.g. a MouseEvent)? In other words can an event handler be executed before the enqueued Runnable, if the user event happend after EventQueue.invokeLater.

Thanks!

Plead answered 25/1, 2013 at 17:19 Comment(4)
No. but having to rely in which order user input events are ordered is the sign of a problematic design. If you are in the process of dispatching an event, many other input events may have already reached the queue.Pecoraro
The javadoc says: Causes runnable to have its run method called in the dispatch thread of the system EventQueue. This will happen after all pending events are processed. (emphasis mine)Pentaprism
That statement was not clear to me. Does it refer only to currently processing Events or also pending events that are submitted after the enqued Runnable? When exactly do events become pending? When they get dispatched from the queue or at the time they are enqueued. Do events join the queue like the InvocationEvents or are the InvocationEvents with the Runnables generally executed subsequent?Plead
Events are pending when they are on the EventQueue and not yet dispatched. So when in the process of dispatching one event, several others may be already waiting to be dispatched. Then you will try to push your InvocationEvent which will be run after all the pending events have been dispatched. Anyway, you should not have to rely on that.Pecoraro
M
6

Probably it's best to explain what you're trying to do. In general, the event dispatch system is designed such that you shouldn't know or worry about its internals. If you are writing an event handler that directly updates the UI then it may make sense to not use a Runnable and perform your updates inline. However if your event handler performs some calculations or processing that takes an indeterminate amount of time, then you would want to use a thread which would eventually perform the UI update with a Runnable with the Swing.invokeLater call. Needing to know the sequence of events as they are dispatched means you are attempting to build assumptions in your logic that probably belong elsewhere.

For eg, consider a mouse click event handler that performs heavy processing (network downloads or DBMS queries) in a thread and later updates a text label in the UI. You probably want to ensure the processing and UI update happens before the user clicks elsewhere on the UI to avoid ambiguity in which clicks are processed. In such a case you would likely disable the UI or certain clickable parts of the UI immediately in the initial mouse click event so that the UI stays responsive but protects your onMouse handler from re-entry until the original click event is fully processed. (forgive the following snippet as it has been years since I've done Swing, it's more like pseudo-code.)

void setUIEnabled(boolean enable) {
   for(Button eachUIButton : getAllClickableButtons()) {
      eachUIButton.setEnabled(enable);
   }
}

void onMouseClick(MouseEvent event) {
   setUIEnabled(false);
   new Thread(
      new Runnable() {
         public void run() {
            final String result = doLengthyProcessing();
            enableRunnable = new Runnable() {
               public void run() {
                  setUIEnabled(true);
                  updateTextLabelFromLengthyProcess(result);
               }
            };
            Swing.invokeLater(enableRunnable);
         }
      }
   ).start();
}

The idea here is that you write you logic such that it doesn't depend on the random order that events are dispatched, rather it acknowledges that the processing will likely take longer than the next incoming mouse event and makes provisions for such timing. A subtle improvement would be to also draw a progress indicator or progress spinner to indicate work is being done to the user.

Malvia answered 25/1, 2013 at 17:38 Comment(8)
I have a background thread, that disables part of the UI. Until now I used invokeAndWait to shut down the UI and guarantee that it will be shut down when that background thread finished. Recently I refactored the background thread so it uses invokeLater instead of invokeAndWait in order to avoid a deadlocking possibility, and wanted to check if that guarantee of UI beeing disabled on finished thread still holds. However I may have to rethink this, because even then there could still be a user event currently being processed and the background thread could finish before that. Thank you!Plead
If I understand you correctly your background thread used invokeAndWait? That is different than what I suggested above. Don't perform the shut down in the background thread, instead perform it as the very 1st thing in the event handler. The background thread would then use invokeLater to re-enable the UI as the last thing it does before it finishes. Please revisit my above example where I setEnable(false) as the 1st thing in my event handler and I do setEnable(true) in my invokeLater as the LAST thing in my runnable.Malvia
Again, let me be a little more clear. The event handler should immediately disable the UI before spawning the background thread. The background thread should not care when Swing events are processed because the UI is already disabled by the event handler. It just goes about doing what it has to do then it posts a runnable that re-enables the UI when it finishes.Malvia
Shutting down the UI directly from the listeners does not fit the current application design very well. It uses a state machine (there are multiple parts of the ui that can put the application in CLOSING state, and when the clanup has finished it is in CLOSED state, while there are other states too), and that does not mix well with keeping the UI consistent to the state machine with runLater, but I agree that uptating the ui as fist statement in the listener is actually how it should be done. Thank you for this excellent answer, as well as the code sample.Plead
Thank you for accepting my answer. Good luck with refactoring your state machine. It sounds rather involved. I'm not sure I follow why performing the setEnable(false) in the event handler would not work, though I truly understand how designs based on inconsistent state machines become problematic. I'm guessing part of the state machine is controlled (incorrectly) by the state of the UI. In other words I'm imagining your app determines its state by examining the UI rather than having a true UI independent state machine. (That's usually the root cause when the simpler approach cannot be used.)Malvia
The state has to be kept consistent with the view. So UI manipulation has to happen during the state change. The problem is that the state machine is not always manipulated by the EDT, but the UI has to be manipulated from EDT. So it becomes tricky when you don't want to rely on invokeAndWait because then the UI might change some time after the state has already been updated.Plead
You might ask youself why I don't want to use invokeAndWait. That is because if the EDT is trying to lock a monitor and a background thread, that holds this exact monitor tries changing the state, a deadlock occurs. The EDT will never get the monitor and the state changing thread can never release it because it's unable to invoke the Runnable in the EDT.Plead
"The problem is that the state machine is not always manipulated by the EDT" The core problem sounds like the state is not contained in any model, rather it is interpreted from different widgets in the UI. If the state is neatly tucked inside a model object then stimuli from the UI will naturally cause it to change accordingly. The issue then becomes a simple need to bind elements of the UI to the model as it internally updates its state.Malvia
P
10

The API docs state that events are

In the same order as they are enqueued.

But if you check the source code, you'll see that that isn't always the case although it is largely correct for most purposes.

Photodisintegration answered 25/1, 2013 at 17:28 Comment(3)
Are AWTEvents enqueued like the InvocationEvents from the Runnables though or are they immediately dispatched. There are separate methods for AWTEvents, namely dispatchEvent() and postEvent() and I am not sure if they actually enqueuing Events before processing?Plead
The can't be immediately dispatched. Events are dispatched on the AWT Event Dispatch Thread (EDT). The calling thread doesn't block. The event is always queued. Events are enqueued with postEvent and later dequeued and sent to dispatchEvent one at a time in the EDT.Photodisintegration
@Plead in most cases all event are visually (on the screen) done in one momentBrannon
M
6

Probably it's best to explain what you're trying to do. In general, the event dispatch system is designed such that you shouldn't know or worry about its internals. If you are writing an event handler that directly updates the UI then it may make sense to not use a Runnable and perform your updates inline. However if your event handler performs some calculations or processing that takes an indeterminate amount of time, then you would want to use a thread which would eventually perform the UI update with a Runnable with the Swing.invokeLater call. Needing to know the sequence of events as they are dispatched means you are attempting to build assumptions in your logic that probably belong elsewhere.

For eg, consider a mouse click event handler that performs heavy processing (network downloads or DBMS queries) in a thread and later updates a text label in the UI. You probably want to ensure the processing and UI update happens before the user clicks elsewhere on the UI to avoid ambiguity in which clicks are processed. In such a case you would likely disable the UI or certain clickable parts of the UI immediately in the initial mouse click event so that the UI stays responsive but protects your onMouse handler from re-entry until the original click event is fully processed. (forgive the following snippet as it has been years since I've done Swing, it's more like pseudo-code.)

void setUIEnabled(boolean enable) {
   for(Button eachUIButton : getAllClickableButtons()) {
      eachUIButton.setEnabled(enable);
   }
}

void onMouseClick(MouseEvent event) {
   setUIEnabled(false);
   new Thread(
      new Runnable() {
         public void run() {
            final String result = doLengthyProcessing();
            enableRunnable = new Runnable() {
               public void run() {
                  setUIEnabled(true);
                  updateTextLabelFromLengthyProcess(result);
               }
            };
            Swing.invokeLater(enableRunnable);
         }
      }
   ).start();
}

The idea here is that you write you logic such that it doesn't depend on the random order that events are dispatched, rather it acknowledges that the processing will likely take longer than the next incoming mouse event and makes provisions for such timing. A subtle improvement would be to also draw a progress indicator or progress spinner to indicate work is being done to the user.

Malvia answered 25/1, 2013 at 17:38 Comment(8)
I have a background thread, that disables part of the UI. Until now I used invokeAndWait to shut down the UI and guarantee that it will be shut down when that background thread finished. Recently I refactored the background thread so it uses invokeLater instead of invokeAndWait in order to avoid a deadlocking possibility, and wanted to check if that guarantee of UI beeing disabled on finished thread still holds. However I may have to rethink this, because even then there could still be a user event currently being processed and the background thread could finish before that. Thank you!Plead
If I understand you correctly your background thread used invokeAndWait? That is different than what I suggested above. Don't perform the shut down in the background thread, instead perform it as the very 1st thing in the event handler. The background thread would then use invokeLater to re-enable the UI as the last thing it does before it finishes. Please revisit my above example where I setEnable(false) as the 1st thing in my event handler and I do setEnable(true) in my invokeLater as the LAST thing in my runnable.Malvia
Again, let me be a little more clear. The event handler should immediately disable the UI before spawning the background thread. The background thread should not care when Swing events are processed because the UI is already disabled by the event handler. It just goes about doing what it has to do then it posts a runnable that re-enables the UI when it finishes.Malvia
Shutting down the UI directly from the listeners does not fit the current application design very well. It uses a state machine (there are multiple parts of the ui that can put the application in CLOSING state, and when the clanup has finished it is in CLOSED state, while there are other states too), and that does not mix well with keeping the UI consistent to the state machine with runLater, but I agree that uptating the ui as fist statement in the listener is actually how it should be done. Thank you for this excellent answer, as well as the code sample.Plead
Thank you for accepting my answer. Good luck with refactoring your state machine. It sounds rather involved. I'm not sure I follow why performing the setEnable(false) in the event handler would not work, though I truly understand how designs based on inconsistent state machines become problematic. I'm guessing part of the state machine is controlled (incorrectly) by the state of the UI. In other words I'm imagining your app determines its state by examining the UI rather than having a true UI independent state machine. (That's usually the root cause when the simpler approach cannot be used.)Malvia
The state has to be kept consistent with the view. So UI manipulation has to happen during the state change. The problem is that the state machine is not always manipulated by the EDT, but the UI has to be manipulated from EDT. So it becomes tricky when you don't want to rely on invokeAndWait because then the UI might change some time after the state has already been updated.Plead
You might ask youself why I don't want to use invokeAndWait. That is because if the EDT is trying to lock a monitor and a background thread, that holds this exact monitor tries changing the state, a deadlock occurs. The EDT will never get the monitor and the state changing thread can never release it because it's unable to invoke the Runnable in the EDT.Plead
"The problem is that the state machine is not always manipulated by the EDT" The core problem sounds like the state is not contained in any model, rather it is interpreted from different widgets in the UI. If the state is neatly tucked inside a model object then stimuli from the UI will naturally cause it to change accordingly. The issue then becomes a simple need to bind elements of the UI to the model as it internally updates its state.Malvia

© 2022 - 2024 — McMap. All rights reserved.