简体   繁体   English

Java Swing中的奇怪并发问题

[英]Strange Concurrency Issue in Java Swing

I have a Jframe which is my application's window (appFrame in the following code) that contains a lot of logic and takes like 1 second or so to load. 我有一个Jframe,它是我的应用程序窗口(以下代码中的appFrame),它包含大量逻辑并需要大约1秒左右才能加载。 In the meantime I want to show my user a very nice loading frame (initFrame). 在此期间,我想向我的用户展示一个非常好的加载框架(initFrame)。 However, when I run this code, the initFrame does appear but the text in a JLabel on it doesn't appear immediately - it actually doesn't appear at all in the brief moment till the app frame is loaded. 但是,当我运行此代码时,initFrame会出现,但是JLabel上的文本不会立即出现 - 它实际上在短暂的时刻根本不会出现,直到加载应用程序框架。

If i comment out all the appFrame, and only launch the initFrame, the text is loaded instantly, no waiting time at all. 如果我注释掉所有appFrame,并且只启动initFrame,文本会立即加载,根本没有等待时间。 Why is this so? 为什么会这样? Might this be a concurrency issue? 这可能是并发问题吗?

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() { //as per best practice for concurrency in swing - see http://docs.oracle.com/javase/tutorial/uiswing/concurrency/
        @Override
        public void run() {
            final JFrame initFrame = new InitFrame();
            initFrame.setVisible(true);
            final AppFrame appFrame = new AppFrame();
            appFrame.setVisible(true);
            initFrame.setVisible(false);
            initFrame.dispose();
        }
    });

}

I would separate the frames' creation into two threads. 我会将帧的创建分成两个线程。 The first, initializing InitFrame . 第一个,初始化InitFrame Running this thread and calling isShowing() on the InitFrame object. 运行此线程并在InitFrame对象上调用isShowing() When it returns true, run the second thread to initialize and show AppFrame . 当它返回true时,运行第二个线程来初始化并显示AppFrame

This will force a happens before relationship between the visibility of the two frames. 这将强制在两个帧的可见性之间的关系之前发生。

class Main {
    JFrame initFrame = null;
    AppFrame appFrame = null;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
               initFrame = new InitFrame();
               initFrame.setVisible(true);
            }
        });

        while(!initFrame.isShowing()) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
            }
        }

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                appFrame = new AppFrame();
                appFrame.setVisible(true);
                initFrame.setVisible(false);
                initFrame.dispose();
            }
        });

    }
}

Here's an example of what might be going wrong in your AppFrame . 以下是AppFrame可能出现问题的示例。

You can run the test with threading: 您可以使用线程运行测试:

java SplashTest true

or without 或没有

java SplashTest

When threading is enabled, you see the SplashFrame and AppFrame updating every 250ms, more or less. 启用线程后,您会看到SplashFrameAppFrameSplashFrame更新或更少。

When threading is not enabled, you get to see the SplashFrame with no components showing, the app 'hangs' for 4 seconds, then you see the AppFrame . 如果未启用线程,您将看到没有显示任何组件的SplashFrame ,应用程序“挂起”4秒,然后您会看到AppFrame

The example is somewhat contrived, but might give you some ideas. 这个例子有点人为,但可能会给你一些想法。

Note that the SplashFrame has no 'direct' connection to the AppFrame . 请注意, SplashFrameAppFrame没有“直接”连接。 All communication is through the AppFrameWorkListener interface. 所有通信都通过AppFrameWorkListener接口完成。

I've also put the 'work' in the AppFrame . 我还把'work'放在了AppFrame But really if there is a lot of processing to be done it should be extracted out of the UI code, run in a separate Thread , and the AppFrame would be notified of progress by the task, in the same way as the SplashFrame currently is. 但实际上,如果要进行大量处理,则应该从UI代码中提取,在单独的Thread运行,并且AppFrame将被通知任务的进度,与SplashFrame当前的方式相同。

import javax.swing.*;

class SplashTest {

    static boolean useThread = false;

    public static void main(String[] args) {
        // Pass true at the command line to turn on threading.
        // No args, or any value other than true will turn off threading.
        if (args.length > 0) {
            useThread = new Boolean(args[0]);
        }
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                SplashFrame splashFrame = new SplashFrame();
                splashFrame.setVisible(true);
                new AppFrame(splashFrame).setVisible(true);
            }});
    }

    private static class BaseFrame extends JFrame {
        public BaseFrame() {
            setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            setSize(200, 200);
            setTitle(getClass().getSimpleName());
        }
    }

    private static class SplashFrame extends BaseFrame implements AppFrameWorkListener {
        JLabel status;

        public SplashFrame() {
            setLocation(0, 0);
            status = new JLabel("Splash Frame");
            getContentPane().add(status);
        }

        public void appFrameWorkStart() {
            status.setText("Work started");
        }

        public void appFrameWorkProgress(long timeElapsed) {
            status.setText("Work has taken " + timeElapsed + "ms so far");
        }

        public void appFrameWorkDone() {
            // http://stackoverflow.com/questions/1234912/how-to-programmatically-close-a-jframe
            setVisible(false);
            dispose();
        }
    }

    private static class AppFrame extends BaseFrame {
        JLabel status;
        AppFrameWorkListener listener;

        public AppFrame(AppFrameWorkListener listener) {
            setLocation(200, 200);
            status = new JLabel("App Frame");
            getContentPane().add(status);

            this.listener = listener;

            // None of this 'heavy lifting' should be in a constructor.
            if (useThread) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        doLotsOfWork(4);
                    }
                }).start();
            } else {
                doLotsOfWork(4);
                onWorkDone();
            }
        }

        private void doLotsOfWork(int workLengthSeconds) {
            // We're starting. Ensure onWorkStart is called on the EDT,
            // as this method may be called from a different Thread.
            invokeOnWorkStartOnEDT();

            long start = System.currentTimeMillis();

            // Hammer the CPU for "workLengthSeconds" number of seconds.
            // And do some contrived progress reporting.
            long workLengthMs = workLengthSeconds * 1000;
            while (System.currentTimeMillis() - start < workLengthMs) {
                long innerStart = System.currentTimeMillis();
                // Consume 250ms CPU before issuing progress update.
                while (System.currentTimeMillis() - innerStart < 250);
                invokeOnWorkProgressOnEDT(System.currentTimeMillis() - start);
            }

            // We're done now. Ensure onWorkDone is called on the EDT,
            // as this method may be called from a different Thread.
            invokeOnWorkDoneOnEDT();
        }

        private void invokeOnWorkStartOnEDT() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    onWorkStart();
                }
            });
        }

        private void invokeOnWorkProgressOnEDT(final long timeElapsed) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    onWorkProgress(timeElapsed);
                }
            });
        }

        private void invokeOnWorkDoneOnEDT() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    onWorkDone();
                }
            });
        }

        private void onWorkStart() {
            status.setText("Work Started");
            if (null != listener) {
                // Tell someone who's interested in the work status.
                listener.appFrameWorkStart();
            }
        }

        private void onWorkProgress(long timeElapsed) {
            status.setText("Work has taken " + timeElapsed + "ms so far");
            if (null != listener) {
                // Tell someone who's interested in the work status.
                listener.appFrameWorkProgress(timeElapsed);
            }
        }

        private void onWorkDone() {
            status.setText("Work Done");
            if (null != listener) {
                // Tell someone who's interested in the work status.
                listener.appFrameWorkDone();
            }
        }
    }

    interface AppFrameWorkListener {
        public void appFrameWorkDone();
        public void appFrameWorkStart();
        public void appFrameWorkProgress(long timeElapsed);
    }

}

You Should use Java Thread and you can show an interactive Splash Screen (Custom made) to your user in the mean while while your code is generating whatever you want here is a tutorial just take a look 你应该使用Java Thread,你可以向你的用户显示一个交互式的Splash Screen(自定义),而你的代码生成任何你想要的东西是一个教程只是看看

You should use Threads for good and efficient concurrency thats it 你应该使用Threads来实现良好和高效的并发性

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM