so.very.tired
so.very.tired

Reputation: 3086

Java: NullPointerException when trying to update SWT Label from a different thread

I understand, from reading here in SO and from other sources online (this for example), that you cannot access SWT objects from a different thread.
Indeed, when I try to do this:

Class Menu:

public class Menu {

    public static void main(String args[]) {

        System.out.println("Main thread: "+Thread.currentThread().getId()); // prints 1

        final Display display=new Display();
        final Shell shell=new Shell(display);
        Label l=new Label(shell, SWT.SHADOW_IN);
        l.setText("Some label");
        l.pack();

        SecondThread second_thread=new SecondThread(l);
        Thread t=new Thread(second_thread);
        t.start();

        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        display.dispose();
    }
}

Class SecondThread:

public class SecondThread implements Runnable {

    private Label label;
    public SecondThread(Label l) {
        label=l;
    }

    @Override
    public void run() {
        System.out.println("Second thread: "+Thread.currentThread().getId()); // prints 8

        for (int i=0; i<10000; ++i) {

            try {
                Thread.sleep(3000); // waiting three seconds...
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            label.setText("i is: "+i); // SWTException is thrown here
        }
    }
}

It throws SWTException:

Exception in thread "Thread-0" org.eclipse.swt.SWTException: Invalid thread access
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.widgets.Widget.error(Unknown Source)
    at org.eclipse.swt.widgets.Widget.checkWidget(Unknown Source)
    at org.eclipse.swt.widgets.Label.setText(Unknown Source)
    at tst.SecondThread.run(SecondThread.java:25)
    at java.lang.Thread.run(Thread.java:745)

The solution suggested here, and in other sources online, is to use Display's asyncExec() method, which:

Causes the run() method of the runnable to be invoked by the user-interface thread at the next reasonable opportunity. The caller of this method continues to run in parallel, and is not notified when the runnable has completed. Specifying null as the runnable simply wakes the user-interface thread when run.

So I tried to replace:

SecondThread second_thread=new SecondThread(l);
Thread t=new Thread(second_thread);
t.start();

With:

SecondThread second_thread=new SecondThread(l);
display.asyncExec(second_thread);

But as it turns out, this not really creates new thread of execution, as the output suggests:

Main thread: 1
Second thread: 1

Consequently, the Thread.sleep(3000) invocation makes the GUI window unresponsive...

So I tried a different approach:

Menu:

public class Menu {

    public static void main(String args[]) {

        System.out.println("Main thread: "+Thread.currentThread().getId()); // prints 1

        final Display display=new Display();
        final Shell shell=new Shell(display);
        Label l=new Label(shell, SWT.SHADOW_IN);
        l.setText("Some label");
        l.pack();

        SecondThread second_thread=new SecondThread(l);
        Thread t=new Thread(second_thread);
        t.start();

        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        display.dispose();
    }
}

SecondThread:

public class SecondThread implements Runnable {

    private Label label;
    public SecondThread(Label l) {
        label=l;
    }

    @Override
    public void run() {
        System.out.println("Second thread: "+Thread.currentThread().getId()); // prints 8

        for (int i=0; i<10000; ++i) {

            try {
                Thread.sleep(3000); // waiting three seconds...
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            updateLabel("i is: "+i);
        }
    }

    private void updateLabel(final String string) {

        Display display=Display.getCurrent(); // display is not null...

        display.asyncExec(new Runnable() { // throws NullPointerException... 

            @Override
            public void run() {
                System.out.println("test");
                label.setText(string);
            }

        });
    }
}

Unfortunately, this throws NullPointerException:

Main thread: 1
Second thread: 8
Exception in thread "Thread-0" java.lang.NullPointerException
    at tst.SecondThread.updateLabel(SecondThread.java:33)
    at tst.SecondThread.run(SecondThread.java:25)
    at java.lang.Thread.run(Thread.java:745)

And I have no idea why...
Does anyone know what might cause this?

Upvotes: 1

Views: 1484

Answers (1)

greg-449
greg-449

Reputation: 111217

Your SecondThread is not the user interface thread so Display.getCurrent() will return null since only the user interface thread has a current display.

Use Display.getDefault() to get the display from any thread.

Upvotes: 3

Related Questions