我使用 SwingWorker 吗?

Do I use a SwingWorker?

当我的代码发送 http 调用时,我希望它更改 window 上的 JLabel(基本上显示在 http 调用结束之前大约剩余多少时间)。由于我在这里问的另一个问题,我一直在研究 SwingWorkers,但我不确定如何使用它。我正在编写的代码基本上有一个循环来发送调用,每次计时 运行 调用需要多长时间,计算剩余的大概时间,然后将其发送到 JLabel (注意 JLabel在不同的实例化对象中)。

大多数 SwingWorker 示例显示了一个在后台继续运行的函数,该函数不受工作线程的影响(例如,一个完全基于时间而不是被代码更改的计数器)。如果是这种情况,那么 JLabel 的更改不只是工作线程的一部分,因为它是通过新循环的代码 运行s -> 计算时间并进行调用 -> 更改 JLabel?我可能错了,但是我如何让代码而不是独立线程更改 JLabel?

我的一个问题是,当我最初设置代码时,JLabel.

中没有任何变化

这是我的代码:

package transcription.windows;

import javax.swing.*;

import java.awt.*;

import static javax.swing.JFrame.EXIT_ON_CLOSE;

public class PleaseWaitWindow {

    private JLabel pleaseWaitJLabel = new JLabel("Please wait");
    private GridBagConstraints containerGbc = new GridBagConstraints();
    private Container contentPaneContainer = new Container();
    private JFrame pleaseWaitJFrame;

    public JLabel getPleaseWaitJLabel() {
        return pleaseWaitJLabel;
    }

    public JFrame setPleaseWaitWindow() {
        pleaseWaitJFrame = new JFrame();
        contentPaneContainer = setContentPane();
        pleaseWaitJFrame.setContentPane(contentPaneContainer);
        pleaseWaitJFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
        pleaseWaitJFrame.setTitle("");
        pleaseWaitJFrame.setSize(350, 150);
        pleaseWaitJFrame.setLocationRelativeTo(null);
        pleaseWaitJFrame.setVisible(true);

        return pleaseWaitJFrame;
    }

    private Container setContentPane() {
        containerGbc.insets.bottom = 1;
        containerGbc.insets.top = 2;
        containerGbc.insets.right = 1;
        containerGbc.insets.left = 1;
        containerGbc.weightx = 1;
        containerGbc.weighty = 1;

        contentPaneContainer.setLayout(new GridBagLayout());
        contentPaneContainer.setSize(800, 700);
        setPleaseWaitJLabel();

        return contentPaneContainer;
    }

    private void setPleaseWaitJLabel() {
        containerGbc.gridx = 2;
        containerGbc.gridy = 5;
        containerGbc.gridwidth = 2;
        containerGbc.gridheight = 1;
        contentPaneContainer.add(pleaseWaitJLabel, containerGbc);
    }

    public void setJLabelDisplay(String displayTime) {
        pleaseWaitJLabel.setText(displayTime);
    }

    public void closeWindow() {
        pleaseWaitJFrame.dispose();
    }
    }

作为 ServiceUpload 一部分的方法 class:

    public String cuttingLoop(String mpBase64Piece, String jobName, String email) {
        Integer numberOfPiecesMinusEnd = (int) Math.ceil(mpBase64Piece.length() / 500000.0);
        List<String> base64List = new ArrayList<>();

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

            if (mpBase64Piece.length() >= 500000) {
                base64List.add(mpBase64Piece.substring(0, 500000));
                mpBase64Piece = mpBase64Piece.substring(500000);
            }

        }
        base64List.add(mpBase64Piece);

        pleaseWaitWindow = new PleaseWaitWindow();
        pleaseWaitWindow.setPleaseWaitWindow();
        for (int n = 0; n < base64List.size(); n++) {
            numberOfLoopsLeft = numberOfPiecesMinusEnd - n;
            Stopwatch stopwatch = null;
            String tag;
            Stopwatch.createStarted();
            if (base64List.get(n) != null) {
                if (n == 0) {
                    tag = "start";
                } else if (n == base64List.size() - 1) {
                    tag = "end";
                } else {
                    tag = "middle";
                }
                stopwatch = Stopwatch.createStarted();
                response = providerUpload.executeUploadHttp(base64List.get(n), jobName, tag, email);
                stopwatch.stop();
            }
            long oneLoopTime = stopwatch.elapsed(TimeUnit.SECONDS);
            pleaseWaitWindow.setJLabelDisplay(numberOfLoopsLeft*oneLoopTime+" seconds remaining");
            LOGGER.info("complete");
        }
        pleaseWaitWindow.closeWindow();
        return response;
    }

我的一个问题是,当 'SwingWorker' 未与上述代码一起使用时,代码未显示 'JLabel'。

最好将您的代码拆分为职责范围。让我们来看看三个: 1. worker(即上传); 2.显示(即JLabel更新); 3. 两者的整合(前两者是相互独立的,所以你需要一些东西把它们绑在一起)。

从实际工作中抽象出来,可以使用标准接口。第一个只是 Runnable,即不带任何参数,也不 returning 任何东西。第二个是 Consumer<String>,因为它需要 String(显示)但不需要 return 任何东西。第三个将是你的主要控制。

让我们从控件开始,因为这很简单:

Consumer<String> display = createDisplay();
Runnable worker = createWorker();
CompletableFuture.runAsync(worker);

这将在一个单独的线程中启动 worker,这听起来就像您想要的那样。

这是您的上传者:

Consumer<String> display = // tbd, see below
Runnable worker = () -> {
    String[] progress = {"start", "middle", "finish"};
    for (String pr : progress) {
        display.accept(pr);
        Thread.sleep(1000); // insert your code here
    }
}

请注意,这个工作人员实际上确实依赖于消费者;这有点“不干净”,但可以。

现在进行展示。将其定义为 Consumer<String>,它足够抽象,我们可以在控制台上打印进度。

Consumer<String> display = s -> System.out.printf("upload status: %s%n", s);

但是您想要更新 JLabel;所以消费者看起来像

Consumer<String> display = s -> label.setText(s);
// for your code
s -> pleaseWaitWindow.getPleaseWaitLabel().setText(s);

您的实际问题

因此,如果您这样做,您会注意到您的标签文本没有如您预期的那样得到更新。那是因为 label.setText(s) 在 worker 所在的线程中执行 运行ning;它需要插入到 Swing 线程中。这就是 SwingWorker 的用武之地。

SwingWorker 有一个 progress 字段,您可以将其用于标签;它还有一个 doInBackground() 这是您实际的上传工作线程。所以你最终得到

class UploadSwingWorker {
    public void doInBackground() {
        for(int i = 0; i < 3; i++) {
            setProgress(i);
            Thread.sleep(1000); // again, your upload code
        }
    }
}

那如何更新您的标签? setProgress 引发了一个 PropertyChangeEvent 你可以拦截;这是使用带有签名

PropertyChangeListener 完成的
void propertyChange(PropertyChangeEvent e)

这是一个函数式接口,因此您可以使用 lambda 来实现它,在您的情况下

String[] displays = {"start", "middle", "finish"};
updateLabelListener = e -> {
    int index = ((Integer) e.getNewValue()).intValue(); // the progress you set
    String value = displays[index];
    label.setText(value);
}

您可以使用

将其添加到SwingWorker
SwingWorker uploadWorker = new UploadSwingWorker();
uploadWorker.addPropertyChangeListener(updateLabelListener);
uploadWorker.execute(); // actually start the worker

更简单

请注意,我自己从未以这种方式使用过 SwingWorker。解决 GUI 未从您的工作线程中更新的问题的更简单方法是使用 SwingUtilities.invokeLater().

调用 GUI 更新

回到最初Consumer<String>我提出来了,你可以的

    Consumer<String> display = s -> SwingUtilities.invokeLater(
        () -> pleaseWaitWindow.getPleaseWaitLabel().setText(s)
    );

应该就可以了。这允许您让您的工作人员保持更抽象的 Runnable 并使用通常的调度机制来 运行 它(例如 ExecutorService.submit()CompletableFuture.runAsync()),同时仍然允许更新GUI 也同样简单。