简体   繁体   English

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

[英]Codename One: Background threads needing access to the UI

My app has background threads that need to access the UI.我的应用程序有需要访问 UI 的后台线程。 Imagine a chess program (AI) that "thinks" for a number of seconds before it plays a move on the board.想象一个国际象棋程序 (AI) 在棋盘上下棋之前“思考”了几秒钟。 While the thread runs the UI is blocked for input but there is still output.当线程运行时,UI 被阻止输入,但仍然存在 output。

There are 3 threads involved:涉及3个线程:

  1. the CN1 EDT CN1 淡香水
  2. the think thread, using invokeAndBlock, that outputs information about the search process (in a TextField), such as the current move, search depth and search value思考线程,使用 invokeAndBlock,输出有关搜索过程的信息(在 TextField 中),例如当前移动、搜索深度和搜索值
  3. a clock thread, started with Thread.start(), that updates once per second the time used by White or Black (TextFields)一个时钟线程,以 Thread.start() 开始,每秒更新一次 White 或 Black (TextFields) 使用的时间

During the search (invokeAndBlock) the stopButton is accessible to force the search to stop (not shown).在搜索(invokeAndBlock)期间,可以访问 stopButton 以强制停止搜索(未显示)。

Below is my current implementation.下面是我目前的实现。 It works and my question is: is it the right way to implement this?它有效,我的问题是:这是实现它的正确方法吗?

(I read https://www.codenameone.com/blog/callserially-the-edt-invokeandblock-part-1.html and part-2.) (我读过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 (?)
}

Edit : new versions of think, showWhiteTime and showBlackTime.编辑: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);
    });
}

Most of the code is fine though I would avoid the EDT violations you have in showWhiteTime and showBlackTime .尽管我会避免您在showWhiteTimeshowBlackTime中遇到的 EDT 违规,但大部分代码都很好。 EDT violations can fail in odd ways all of a sudden since you trigger async operations and things can turn nasty quickly. EDT 违规可能会突然以奇怪的方式失败,因为您触发了异步操作并且事情可能很快变得令人讨厌。 I suggest turning on the EDT violation detection tool in the simulator.我建议在模拟器中打开 EDT 违规检测工具。

Two things to keep in mind when using invokeAndBlock :使用invokeAndBlock时要记住两件事:

  • It's slower than a regular thread它比普通线程慢
  • It blocks pending events in some cases so it's problematic to have it as a part of a pending event chain在某些情况下它会阻止待处理事件,因此将其作为待处理事件链的一部分是有问题的

The second point is a difficult one to grasp and a source of many mistakes so it's worth explaining a bit.第二点很难掌握,也是很多错误的根源,所以值得解释一下。

consider this code:考虑这段代码:

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

That might not seem realistic as you usually don't add two separate listeners one after the other but this happens enough eg if one change triggers another etc.这可能看起来不现实,因为您通常不会一个接一个地添加两个单独的侦听器,但这已经足够了,例如,如果一个更改触发另一个更改等。

The current event processing will be blocked for buttonA during invokeAndBlock .invokeAndBlock期间将阻止buttonA的当前事件处理。 That means that doOtherStuff() will wait for the invokeAndBlock and also doSomethingImportant() will wait.这意味着doOtherStuff()将等待invokeAndBlock并且doSomethingImportant()将等待。

If doSomethingImportant() shows another form you can end up with weird behavior such as ages after you pressed the button and did a lot of other things suddenly your form changes.如果doSomethingImportant()显示另一种形式,您可能会在按下按钮后出现奇怪的行为,例如年龄,并且突然做了很多其他事情,您的形式会发生变化。

So you need to be very conscious of your usage of invokeAndBlock .所以你需要非常注意你对invokeAndBlock的使用。

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

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