This demo shows how to use the EventDispatchThreadMonitor
to respond to an unresponsive event dispatch thread.
Setting up a listener resembles:
EventDispatchThreadMonitor.get().addListener(millisToWait, myListener);
The first argument is the number of milliseconds the event dispatch thread must be unresponsive before myListener
is notified.
The listener receives two types of notifications:
public interface Listener { void becameUnresponsive(Thread eventDispatchThread, long unresponsiveMillis, long lastSuccessfulPingMillis); void becameResponsive(Thread eventDispatchThread, long unresponsiveMillis, long lastSuccessfulPingMillis); }
The EventDispatchThreadMonitor
class sets up a static daemon thread that continually pings SwingUtilities.invokeLater(..)
. The helper thread monitors how long it takes to invoke the runnable, and if it starts to take too long then the listeners are notified.
In Java all user-generated events are channeled through the event dispatch thread. (Among other things: this includes every MouseListener
, KeyListener
, ActionListener
, etc.) If any of these events take too long, then the event dispatch thread cannot move on to process the next several events in its queue. The Java application becomes unresponsive. Users can drag the titlebar, but they can't otherwise interact with the window.
Obviously the best solution to this problem is a great software architecture that insulates everything in separate worker threads so AWTEvents
(and others) can flow into the app as fast as possible. But in my experience given real-world constraints I've found this is not always possible. Developers/managers always have to strike a balance between what would be ideal and what can be released within a specific timeframe. Either because of poor design or feature creep over time: some listeners become (slightly) bad actors that can take too long to complete.
This blocked thread can present to the user in a few different ways.
The most harmless interpretation might be that the user perceives your app as being "slow" or "sluggish". If the event dispatch thread is frequently blocked for small amounts of time but it always recovers: the user may (in some cases) be willing to forgive or tolerate the experience.
If the thread doesn't recover for upwards to 10 seconds, though, users might start to assume that their session is ruined. I'm going to intentionally cite some very old references here to emphasize how well-known this problem is.
Some Win32 documentation states:
... From user research, we know that users get annoyed and frustrated after just a couple of seconds of unresponsiveness. After 5 seconds, they will try to terminate a hung application. Next to crashes, application hangs are the most common source of user disruption when working with Win32 applications.
The basic advice regarding response times has been about the same for [decades]:
0.1 second is about the limit for having the user feel that the system is reacting instantaneously, meaning that no special feedback is necessary except to display the result.
1.0 second is about the limit for the user's flow of thought to stay uninterrupted, even though the user will notice the delay. Normally, no special feedback is necessary during delays of more than 0.1 but less than 1.0 second, but the user does lose the feeling of operating directly on the data.
10 seconds is about the limit for keeping the user's attention focused on the dialogue. For longer delays, users will want to perform other tasks while waiting for the computer to finish, so they should be given feedback indicating when the computer expects to be done. Feedback during the delay is especially important if the response time is likely to be highly variable, since users will then not know what to expect.
Any modern OS (Mac, Windows, Android, etc.) has a system-level dialog that appears if an application becomes unresponsive, and that dialog will ask the user if they want to terminate it or not.
This demo offers three possible responses to a blocked event dispatch thread:
ThreadProfiler
until the event dispatch thread recovers. This can help log what your application is doing while the application is blocked, so developers can review these bottlenecks and improve the user's experience.Note Thread.stop()
is going be unsupported someday. (Or maybe it already is? I'm often a few JDK versions behind the curve.) So obviously when it stops working: this option will stop working too.
Depending on your application you might be able to brainstorm some other Listeners. For example: can you autosave? And/or can you autosave to a special "recovered documents" folder, so on the next launch you can discuss what the user wants?