【问题标题】:Java: How to start some asyncronous operations and update the ui from other thread?Java:如何启动一些异步操作并从其他线程更新 ui?
【发布时间】:2026-01-23 21:15:02
【问题描述】:

我有一个小问题,现在我要处理一个小型 Java 项目。我正在尝试从 Button 事件处理程序运行几个线程;那么最后一个线程必须更新 UI 上的 TextArea。线程正在做计算工作。最重要的部分是让 UI 正常工作 - 我不想冻结 UI,当然我想定期从其他线程之一更新 UI 的 TextArea(不是 ui 的线程)。所以这是我的代码的一部分:

在 Button 的事件处理程序中,我正在启动这 4 个线程:

Thread generate = new CombinaMaker();
generate.run();

Thread forward = new TranslateForward();
forward.run();

Thread backward = new TranslateBackward();
backward.run();

Thread refresh = new Refresher();
refresh.run();

我希望线程同时工作。 Refresher 线程必须定期更新 UI TextArea 组件。

所以,这是我从 Refresher 线程更新 UI 的方法:

public static void updateProgress() 
{
    SwingUtilities.invokeLater(new Runnable() 
    {   
         public void run() 
         {
             //HERE
             //I have a loop that have to be looping at every 500 ms
         }
     }
}

我的问题是:我应该这样做吗?我需要简单有效。这个循环让我很难过,因为只要应用程序正常工作,它就必须循环。这可能是冻结用户界面的原因吗?我知道我在某个地方犯了大错误,但此刻我无法独自找到它。也许你可以建议我一些简单的解决方案。最后一个想法 - 前 4 个线程:我想“同时”启动它们,而不冻结我的 UI。这种方式(从事件处理程序中像这样启动它们......)是正确的方式还是可能更好?非常感谢朋友!

PS。如果通过我的“刷新”线程刷新 UI 是一项艰巨的工作,我已经准备好妥协变体 - 通过他自己的线程(UI 线程)更新 UI。但在这种情况下,我可能必须使用一些“时间拍摄机制”——避免 UI 冻结。你在想什么?

【问题讨论】:

  • 您的代码有什么问题?使用其他线程或 TimerTasks 基本上是你必须做的,是的。
  • 更新过程中的循环 - 如何避免冻结 UI?定时器任务?
  • 这可能是个糟糕的建议,但我已经将视图对象的引用传递给其他线程,然后让这些线程直接将数据附加到视图。对小程序来说足够好。不确定我会在产品或任何东西中发布它。

标签: java multithreading user-interface refresh freeze


【解决方案1】:

SwingUtilities.invokeLater 并未设计为调用长时间运行的线程。 您的问题的一种解决方案可能是this one

您也可以尝试反转循环和 SwingUtilities.invokeLater 调用的嵌套,但这样做的可行性取决于您的其余代码

do {
    // Sleep and other stuff
    SwingUtilities.invokeLater(new Runnable() {
        public void run()  {
        // Update UI
        }
    });
} while(...);

【讨论】:

    【解决方案2】:

    您不想在传递给invokeAndDoLater 的runnable 中循环。请参阅此示例:http://www.javamex.com/tutorials/threads/invokelater.shtml

    【讨论】:

    • 给出的链接,虽然它会成功地使 OP 失明,但没有说明可运行对象内部的循环。 ASAIK 在可运行文件中有一个循环并没有错,就系统而言,这只是另一个线程。唯一的问题是它与事件派发是同步的,所以如果循环没有退出或者yield(),那么它会阻塞事件派发。
    • 许多 swing 对象没有同步,因此不要从其他线程更新它们很重要。 InvokeAndDoLater 的目的是让您在 AWT 更新线程上进行更改(从而避免与 AWT 线程的竞争条件)。您永远不想在该线程上执行繁重的操作(例如循环),因为 UI 将冻结直到操作完成。所以从技术上讲你是对的,没有什么可以阻止你,但如果你想要一个响应式 UI,这是一个非常糟糕的主意。
    【解决方案3】:

    主要问题是您的算法。而不是

    SwingUtilities.invokeLater(new Runnable() {   
        public void run() {
            //HERE
            //I have a loop that have to be looping at every 500 ms
        }
    }
    

    你应该有

    // HERE
    // I have a loop that have to be looping at every 500 ms
    // and when something must be updated in the GUI:
    SwingUtilities.invokeLater(new Runnable() {   
        public void run() {
            // update the GUI as fast as possible
        }
    });
    

    while (true) {
        Thread.sleep(500L);
        String update = getUpdate();
        SwingUtilities.invokeLater(new Runnable() {   
            public void run() {
                textArea.append(update);
            }
        });
    }
    

    传递给SwingUtilities.invokeLater() 的runnable 在事件调度线程中执行。它不能执行长时间运行的任务。无休止地循环是一项非常耗时的任务。

    【讨论】:

      【解决方案4】:

      我不想冻结用户界面,当然

      为不想冻结 UI 点赞。不幸的是

      我想定期从其他线程之一更新 UI 的 TextArea

      不是一个选项。更新 Swing 组件必须在 UI 线程(事件调度线程)上完成。幸运的是,Swing 有一个用于定期更新 UI 的内置类:javax.swing.Timer 类。 Timer 在后台运行,并在 Event Dispatch Thread 上执行,因此非常适合此任务。

      【讨论】: