Skip to content

Forced EDT usage

Mikle edited this page Jan 30, 2020 · 5 revisions

Available since WebLaF v1.2.9 release, updated for WebLaF v1.2.12 release
Requires weblaf-ui module and Java 6 update 30 or any later to run


What is it for?

As you might know from many articles about Swing it is highly recommended to perform any operations with Swing components within Event Dispatch Thread (shortly "EDT"). EDT is pretty much a single thread that runs exclusively for performing any UI -related operations and should be used with care to avoid UI performance issues, but that is a different topic.

Let me emphasize the most important part once again - it is highly recommended to perform all UI operations in EDT, but it is not a must. That means you are not forced to perform all Swing-related operations within EDT and can perform those on any application thread you want which almost never lead to any issues on early stages of development, but can easily become a huge problem later on and could cause a lot of random/phantom exceptions here and there. From my own experience it could even sometimes lead to deadlocks and UI crashes. I would say this is a big flaw in Swing. It was addressed in JavaFX, but due to backward compatibility it will probably never be addressed in Swing.

That is why I decided to address this problem in WebLaF with an optional flag which, if enabled, adds multiple thread checks in the code that is designed to run within EDT only.


What should I know about it?

Flag mentioned above is called forceSingleEventsThread and stored within WebLookAndFeel class. When set to true it ensures that whenever any UI-related operation is called outside of EDT a LookAndFeelException will be thrown, indicating that you should perform that operation inside EDT instead.

It is recommended to setup this flag prior to any operations with WebLaF and UI in general, for example straight in the main method your application starts from.

It might not cover some of the basic Swing operations/methods which WebLaF doesn't have access to, but it will surely help you a lot to indicate places in your code where UI elements are being created/called from a wrong thread.


How to use it?

You can easily enable all events thread checks by setting forceSingleEventsThread flag to true:

WebLookAndFeel.setForceSingleEventsThread ( true );

Now let's look at some practical applications of this feature.

Here is a small code sample that would normally never throw any exceptions:

public class ForceEventDispatchThread
{
    public static void main ( final String[] args )
    {
        WebLookAndFeel.install ();
        TestFrame.show ( new WebButton ( "It's working!" ), 50 );
    }
}

But design-wise it is incorrect. Why? Because the thread main method is running within is not an EDT, so performing any Swing-related operations in it is incorrect.

Let's try enabling the flag to see if it will indicate the problem:

public class ForceEventDispatchThread
{
    public static void main ( final String[] args )
    {
        WebLookAndFeel.setForceSingleEventsThread ( true );
        WebLookAndFeel.install ();
        TestFrame.show ( new WebButton ( "It's working!" ), 50 );
    }
}

And we get an exception:

Exception in thread "main" com.alee.laf.LookAndFeelException: This operation is only permitted on the Event Dispatch Thread. Current thread is: main
	at com.alee.laf.WebLookAndFeel.checkEventDispatchThread(WebLookAndFeel.java:1041)
	at com.alee.laf.WebLookAndFeel.install(WebLookAndFeel.java:979)
	at com.alee.laf.WebLookAndFeel.install(WebLookAndFeel.java:944)
	at com.alee.wiki.edt.ForceEventDispatchThread.main(ForceEventDispatchThread.java:33)

So basically our example already failed upon trying to install L&F outside of the EDT.

Now let's fix our code to do things in the right way:

public class ForceEventDispatchThread
{
    public static void main ( final String[] args )
    {
        WebLookAndFeel.setForceSingleEventsThread ( true );
        SwingUtilities.invokeLater ( new Runnable ()
        {
            @Override
            public void run ()
            {
                WebLookAndFeel.install ();
                TestFrame.show ( new WebButton ( "It's working!" ), 50 );
            }
        } );
    }
}

Exception is not thrown anywhere anymore as we are now working with the UI in EDT.

This might look simple, but for more complex applications you might actually have to split threads where you load your application data and where you initialize the UI. Otherwise you will either have UI hanging while data is loaded, if you will be loading it in EDT, or you will have issues with the UI at some point if you work with it outside of EDT.


Custom behavior

I mentioned above that if you set the flag to true an exception would be thrown whenever UI-related operation is called outside of EDT, but that is simply a default behavior. You can setup your own behavior by creating NonEventThreadHandler interface implementation, for example:

public class ForceEventDispatchThread
{
    public static void main ( final String[] args )
    {
        WebLookAndFeel.setNonEventThreadHandler ( new NonEventThreadHandler ()
        {
            @Override
            public void handle ( final RuntimeException e )
            {
                SwingUtilities.invokeLater ( new Runnable ()
                {
                    @Override
                    public void run ()
                    {
                        final JTextArea textArea = new JTextArea ( );
                        textArea.setText ( ExceptionUtils.getStackTrace ( e ) );
                        final JScrollPane scrollPane = new JScrollPane ( textArea );
                        JOptionPane.showMessageDialog ( null, scrollPane, e.getMessage (), JOptionPane.ERROR_MESSAGE );
                    }
                } );
            }
        } );
        WebLookAndFeel.setForceSingleEventsThread ( true );
        WebLookAndFeel.install ();
    }
}

Dialog that handles exception must also be used within EDT, otherwise you risk getting another exception if, for instance, L&F was already installed and something else caused the current exception.

As a result you will see next error dialog:

Custom handler

That could be quite useful to avoid breaking UI with thrown exception and receiving stack trace straight away. It might also be a much better option in case you want to deploy your application to end user, but in general I would recommend switching the forceSingleEventsThread flag off or silently logging any occurred exceptions.

Either way - handling such exceptions should be approached with care because they might sometimes be thrown in a rapid succession, for instance if they occur in some renderer that is being actively used. Handling these exceptions with a dialog as shown above might cause UI to be completely locked off once you reach some place in your application where that exception occurs very often.