当Task正在运行时,我想将updateMessage()中的消息附加到TextArea
因为绑定不会将新文本追加到TextArea,所以我使用了ChangeListener
worker.messageProperty().addListener((observable,oldValue,newValue) -> { ta_Statusbereich.appendText("\n" + newValue); });
这是有效的,但不是每一次改变.
我用System.out.println()检查了它,并在1到300的任务中计算
for (Integer i = 1; i <= 300; i++) { updateMessage(i.toString()); System.out.println(i.toString()); }
任务中的println()给了我想要的1,2,3,4,5,6,7,8等等,但是我的TextArea显示了1,8,9
然后我在ChangeListener中添加了一个println并得到了相同的结果,1,9
(结果随机不总是1,5 ……)
为什么?有没有其他方法将消息文本附加到TextAres,也许与绑定?
解决方法
updateMessage()
表示:
Calls to updateMessage are coalesced and run later on the FX
application thread,so calls to updateMessage,even from the FX
Application thread,may not necessarily result in immediate updates to
this property,and intermediate message values may be coalesced to
save on event notifications.
(我的重点).因此,简而言之,传递给updateMessage(…)的某些值可能永远不会被设置为messageProperty的值,如果它们被另一个值快速取代的话.通常,每次将帧渲染到屏幕时(每秒60次或更少),您只能观察到一个值.如果你有一个重要的用例,你想要观察每个值,那么你需要使用另一种机制.
一个非常天真的实现只会使用Platform.runLater(…)并直接更新文本区域.我不推荐这种实现方式,因为您可能会因为调用过多而导致FX应用程序线程泛滥(updateMessage(…)合并调用的确切原因),导致UI无响应.但是,此实现看起来像:
for (int i = 1 ; i <= 300; i++) { String value = "\n" + i ; Platform.runLater(() -> ta_Statusbereich.appendText(value)); }
另一种选择是使每个操作成为一个单独的任务,并在一些执行器中并行执行它们.附加到每个任务的onSucceeded处理程序的文本区域.在此实现中,结果的顺序不是预先确定的,因此如果顺序很重要,则这不是一个合适的机制:
final int numThreads = 8 ; Executor exec = Executors.newFixedThreadPool(numThreads,runnable -> { Thread t = Executors.defaultThreadFactory().newThread(runnable); t.setDaemon(true); return t ; }); // ... for (int i = 1; i <= 300; i++) { int value = i ; Task<String> task = new Task<String>() { @Override public String call() { // in real life,do real work here... return "\n" + value ; // value to be processed in onSucceeded } }; task.setOnSucceeded(e -> ta_Statusbereich.appendText(task.getValue())); exec.execute(task); }
如果您想从单个任务完成所有这些操作并控制顺序,那么您可以将所有消息放入BlockingQueue,从阻塞队列中获取消息并将它们放在FX Application线程的文本区域中.为了确保不会使用过多的调用来泛洪FX应用程序线程,您应该每帧渲染一次消息来自队列的消息不超过一次呈现给屏幕.您可以使用AnimationTimer
来实现此目的:保证每帧渲染时调用一次handle方法.这看起来像:
BlockingQueue<String> messageQueue = new LinkedBlockingQueue<>(); Task<Void> task = new Task<Void>() { @Override public Void call() throws Exception { final int numMessages = 300 ; Platform.runLater(() -> new MessageConsumer(messageQueue,ta_Statusbereich,numMessages).start()); for (int i = 1; i <= numMessages; i++) { // do real work... messageQueue.put(Integer.toString(i)); } return null ; } }; new Thread(task).start(); // or submit to an executor... // ... public class MessageConsumer extends AnimationTimer { private final BlockingQueue<String> messageQueue ; private final TextArea textArea ; private final numMessages ; private int messagesReceived = 0 ; public MessageConsumer(BlockingQueue<String> messageQueue,TextArea textArea,int numMessages) { this.messageQueue = messageQueue ; this.textArea = textArea ; this.numMessages = numMessages ; } @Override public void handle(long now) { List<String> messages = new ArrayList<>(); messagesReceived += messageQueue.drainTo(messages); messages.forEach(msg -> textArea.appendText("\n"+msg)); if (messagesReceived >= numMessages) { stop(); } } }