代号一:后台线程需要访问 UI
Codename One: Background threads needing access to the UI
我的应用有需要访问 UI 的后台线程。
想象一个国际象棋程序 (AI) 在棋盘上下棋之前“思考”几秒钟。
当线程运行时,UI 被阻止输入,但仍有输出。
涉及3个线程:
- CN1 EDT
- 思考线程,使用 invokeAndBlock,输出有关搜索过程的信息(在 TextField 中),例如当前移动、搜索深度和搜索值
- 一个时钟线程,以 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);
});
}
大部分代码都很好,但我会避免 showWhiteTime
和 showBlackTime
中的 EDT 违规。 EDT 违规可能会突然以奇怪的方式失败,因为您触发了异步操作,事情会很快变得糟糕。建议开启模拟器中的EDT违规检测工具
使用invokeAndBlock
时要记住两件事:
- 它比普通线程慢
- 它在某些情况下会阻止未决事件,因此将其作为未决事件链的一部分是有问题的
第二点很难理解,也是很多错误的来源,所以值得解释一下。
考虑这段代码:
buttonA.addActionListener(e -> {
doStuff();
invokeAndBlock(...);
doOtherStuff();
});
buttonA.addActionListener(e -> doSomethingImportant());
这可能看起来不现实,因为您通常不会一个接一个地添加两个单独的侦听器,但这种情况已经足够了,例如如果一个变化触发另一个变化等等
在 invokeAndBlock
期间,buttonA
的当前事件处理将被阻止。这意味着 doOtherStuff()
将等待 invokeAndBlock
并且 doSomethingImportant()
也会等待。
如果 doSomethingImportant()
显示另一个表单,您可能会出现奇怪的行为,例如在您按下按钮并做了很多其他事情后,您的表单突然发生变化。
所以你需要非常注意你对invokeAndBlock
的使用。
我的应用有需要访问 UI 的后台线程。 想象一个国际象棋程序 (AI) 在棋盘上下棋之前“思考”几秒钟。 当线程运行时,UI 被阻止输入,但仍有输出。
涉及3个线程:
- CN1 EDT
- 思考线程,使用 invokeAndBlock,输出有关搜索过程的信息(在 TextField 中),例如当前移动、搜索深度和搜索值
- 一个时钟线程,以 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);
});
}
大部分代码都很好,但我会避免 showWhiteTime
和 showBlackTime
中的 EDT 违规。 EDT 违规可能会突然以奇怪的方式失败,因为您触发了异步操作,事情会很快变得糟糕。建议开启模拟器中的EDT违规检测工具
使用invokeAndBlock
时要记住两件事:
- 它比普通线程慢
- 它在某些情况下会阻止未决事件,因此将其作为未决事件链的一部分是有问题的
第二点很难理解,也是很多错误的来源,所以值得解释一下。
考虑这段代码:
buttonA.addActionListener(e -> {
doStuff();
invokeAndBlock(...);
doOtherStuff();
});
buttonA.addActionListener(e -> doSomethingImportant());
这可能看起来不现实,因为您通常不会一个接一个地添加两个单独的侦听器,但这种情况已经足够了,例如如果一个变化触发另一个变化等等
在 invokeAndBlock
期间,buttonA
的当前事件处理将被阻止。这意味着 doOtherStuff()
将等待 invokeAndBlock
并且 doSomethingImportant()
也会等待。
如果 doSomethingImportant()
显示另一个表单,您可能会出现奇怪的行为,例如在您按下按钮并做了很多其他事情后,您的表单突然发生变化。
所以你需要非常注意你对invokeAndBlock
的使用。