Java:尝试从不同线程更新 SWT 标签时出现 NullPointerException

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

我明白,从这里阅读 SO 和其他在线资源(例如 this),您不能从不同的线程访问 SWT 对象。
事实上,当我尝试这样做时:

Class 菜单:

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 第二个线程:

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
        }
    }
}

它抛出 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)

此处以及其他在线资源中建议的解决方案是使用 Display 的 asyncExec() 方法,该方法:

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.

所以我尝试替换:

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

与:

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

但事实证明,这并没有真正创建新的执行线程,如输出所示:

Main thread: 1
Second thread: 1

因此,Thread.sleep(3000) 调用使 GUI window 无响应...

所以我尝试了一种不同的方法:

菜单:

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();
    }
}

第二线程:

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);
            }

        });
    }
}

不幸的是,这会抛出 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)

我不知道为什么...
有谁知道这可能是什么原因造成的?

您的 SecondThread 不是用户界面线程,因此 Display.getCurrent() 将 return null 因为只有用户界面线程具有当前显示。

使用Display.getDefault()从任何线程获取显示。