【问题标题】:How to wait for object creation in EDT without blocking EDT?如何在不阻塞 EDT 的情况下等待 EDT 中的对象创建?
【发布时间】:2009-12-31 20:41:38
【问题描述】:

我正在使用 Java 库中的 API,该 API 从事件调度线程调用,并要求我返回完全初始化的 UI 组件。它看起来像这样:

public JDialog createDialog();

但我只能在从数据库加载后填充对话框,有时可能需要 10 秒。通常我会在后台线程中执行此操作,但由于此方法是从 EDT 调用的,并且由于我必须返回对话框,所以这不起作用。它是一个第三方库,所以我无法更改方法,但是我可以做些什么来避免阻塞 EDT?

【问题讨论】:

    标签: java swing concurrency swingworker edt


    【解决方案1】:

    “已初始化”不一定与“已填充”相同。 “初始化”通常意味着对象已经完全构造,但可能没有任何数据。 “已填充”当然意味着数据存在并且任何数据获取任务都已完成。因此,可以为您的第三方库提供一个完全初始化的 JDialog,而无需任何数据。

    我一直喜欢解决这个问题的方法是创建一个自定义的JDialog,它显示一个忙碌的消息或一个进度条等,然后在另一个线程中请求数据。返回数据时,我用数据替换繁忙消息(在 EDT 上!)。至于如何在后台线程中执行请求,我建议使用 SwingWorkers。我喜欢在我的自定义 JDialog 中使用私有 SwingWorker,它在 doInBackground() 方法中处理请求,并在 done() 方法中处理与显示相关的任务。这样做将确保与显示相关的任务仅发生在 EDT 上,而与数据库相关的任务仅发生在 EDT 之外。如果您想对使用 SwingWorkers 进行相当好的介绍,请查看 Sun's tutorial on worker threads。一个简单的例子是:

    public class DBDIalog extends JDialog{
         private JLabel busyLabel = new JLabel("Fetching data from DataBase");
         
         public DBDialog(){
             //do your initialization stuff here
         }
    
         private class DBFetcher extends SwingWorker<Void,DBInfo>{
             
            @Override
            protected DBInfo doInBackground() throws Exception{
                return fetchDataFromDB(); //or whatever database call to make
            }
    
            @Override
            protected void done(){
               try{
                   DBInfo info = get();
               //replace your busy label with your DBInfo
               }catch(InterruptedException e){
                  //do appropriate thread interrupted stuff
               }catch(ExecutionException e){
                  //do appropriate general error handling stuff 
               }
    
            }
         }
    }
    

    有几件事情要记住:done() 方法不是抽象的,所以你不需要重写它。不过,你应该。如果您的doInBackground() 实现抛出异常,除非done() 被覆盖,否则该异常将被吞没。此外,除非您使用SwingUtilities.invokeLater(Runnable),否则不要从doInBackground() 内部更改您的GUI,因为doInBackground() 是从与EDT 不同的线程执行的,并且从后台线程进行GUI 更改要求奇怪和莫名其妙的错误。

    什么时候应该使用它?与其他编程任务不同,在 GUI 中响应时间过长的时间点要短得多——我通常看到写下来的数字约为 250 毫秒。如果您的任务花费的时间比这更长,它应该在后台线程中。在你的情况下,10 秒应该肯定在后台线程中,但是你已经知道了:)

    编辑:

    看到您的评论,我发现我的大部分帖子都没有实际意义。但是,您仍然可以使用 SwingWorker:

    让您的 SwingWorker 执行数据检索,并在 done() 方法中,让它根据数据构造 JDialog 并将该对话框交给您的第三方库。

    【讨论】:

    • 最后一部分是关于“让它从数据中构造 JDialog 并传递对话框......”是困难的。如果在 EDT 上调用我的方法,我如何在后台运行某些东西,然后等待它返回(以便我可以构造并返回对话框),而不阻塞在调用我和我之间发布在 EDT 上的其他事件返回?似乎 SwingWorker 不能解决这个问题。不过,您的答案是两个答案中更好的一个。
    • 您可以覆盖process() 以显示中间结果和/或根据需要调用setProgress()。这是一个示例:sites.google.com/site/drjohnbmatthews/randomdata
    【解决方案2】:

    构建没有数据的对话框,然后启动一个任务来填充它。

    从用户体验的角度来看,从开始到完成需要 10 秒的任何事情都会成为问题。最好立即某事,即使它不是最终形式。如有必要,您可以弹出一个模式对话框,上面只显示“正在加载”。

    【讨论】:

    • 根据这个库的文档,当我返回它时它必须是完全填充的(对不起,如果我之前不清楚这一点)。我不知道如果我之后填充它会发生什么,但我猜这会很糟糕,因为他们指定它必须完全填充。该包的其他文档似乎表明我应该先加载数据,然后填充对话框并返回它,但我不知道如何在不阻塞 EDT 的情况下做到这一点。
    • Scott Fines 给了您一个很好的答案:启动后台进程以加载数据,然后在该进程结束时创建您的对话框(返回 EDT)。为了让用户不会想知道发生了什么,在您检索数据时显示一个临时对话框。
    猜你喜欢
    • 1970-01-01
    • 2015-09-18
    • 1970-01-01
    • 2011-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-05
    相关资源
    最近更新 更多