代号一:后台线程需要访问 UI

Codename One: Background threads needing access to the UI

我的应用有需要访问 UI 的后台线程。 想象一个国际象棋程序 (AI) 在棋盘上下棋之前“思考”几秒钟。 当线程运行时,UI 被阻止输入,但仍有输出。

涉及3个线程:

  1. CN1 EDT
  2. 思考线程,使用 invokeAndBlock,输出有关搜索过程的信息(在 TextField 中),例如当前移动、搜索深度和搜索值
  3. 一个时钟线程,以 Thread.start() 开始,每秒更新一次白色或黑色(文本字段)使用的时间

在搜索 (invokeAndBlock) 期间,可以访问停止按钮以强制停止搜索(未显示)。

下面是我当前的实现。它有效,我的问题是:这是实现它的正确方法吗?

(我阅读了 https://www.codenameone.com/blog/callserially-the-edt-invokeandblock-part-1.html 和第 2 部分。)

Form mainForm;
TextField whiteTime, blackTime;     // updated by clock thread
TextField searchInfo;               // updated by think thread
Clock clock;
Move move;

public void start() {
    ...
    mainForm = new Form(...);
    ...
    thinkButton.addActionListener((ActionListener) (ActionEvent evt) -> {
        think();
    });
    mainForm.show();
}

void think() {
    blockUI();                      // disable buttons except stopButton
    clock.start(board.player);      // this thread calls showWhiteTime or showBlackTime every second
    invokeAndBlock(() -> {          // off the EDT
        move = search(board, time); // e.g. for 10 seconds
    });
    clock.stop();
    animateMove(board, move);
    clock.start(board.player);
    freeUI();
}

// search for a move to play
Move search(Board board, int time) {
    ...
    while (time > 0) {
        ...
        showSearchInfo(info);       // called say a few times per second
    }
    return move;
}

void showSearchInfo(String s) {     // access UI off the EDT
    callSerially(() -> {            // callSerially is necessary here
        searchInfo.setText(s);
    });
}

void showWhiteTime(String s) {
    whiteTime.setText(s);           // no callSerially needed, although off the EDT (?)
}

void showBlackTime(String s) {
    blackTime.setText(s);           // no callSerially needed, although off the EDT (?)
}

编辑:think、showWhiteTime 和 showBlackTime 的新版本。

// version 2, replaced invokeAndBlock by Thread.start() and callSerially
void think() {
    blockUI();                      // disable buttons except stopButton
    new Thread(() -> {              // off the EDT
        clock.start(board.player);  // this thread calls showWhiteTime or showBlackTime every second
        move = search(board, time); // e.g. for 10 seconds
        clock.stop();
        callSerially(() -> {
            animateMove(board, move);
            clock.start(board.player);
            freeUI();
        });
    }).start();
}

// version 2, added callSerially
void showWhiteTime(String s) {      // access UI off the EDT
    callSerially(() -> {
        whiteTime.setText(s);
    });
}

// version 2, added callSerially
void showBlackTime(String s) {      // access UI off the EDT
    callSerially(() -> {
        blackTime.setText(s);
    });
}

大部分代码都很好,但我会避免 showWhiteTimeshowBlackTime 中的 EDT 违规。 EDT 违规可能会突然以奇怪的方式失败,因为您触发了异步操作,事情会很快变得糟糕。建议开启模拟器中的EDT违规检测工具

使用invokeAndBlock时要记住两件事:

  • 它比普通线程慢
  • 它在某些情况下会阻止未决事件,因此将其作为未决事件链的一部分是有问题的

第二点很难理解,也是很多错误的来源,所以值得解释一下。

考虑这段代码:

 buttonA.addActionListener(e -> {
       doStuff();
       invokeAndBlock(...);
       doOtherStuff();
 });
 buttonA.addActionListener(e -> doSomethingImportant());

这可能看起来不现实,因为您通常不会一个接一个地添加两个单独的侦听器,但这种情况已经足够了,例如如果一个变化触发另一个变化等等

invokeAndBlock 期间,buttonA 的当前事件处理将被阻止。这意味着 doOtherStuff() 将等待 invokeAndBlock 并且 doSomethingImportant() 也会等待。

如果 doSomethingImportant() 显示另一个表单,您可能会出现奇怪的行为,例如在您按下按钮并做了很多其他事情后,您的表单突然发生变化。

所以你需要非常注意你对invokeAndBlock的使用。