我正在使用javaFX构建一个GUI应用程序,该应用程序支持长时间运行的CPU密集型操作,如Prime95或Orthos。
我遇到的一个问题是试图让计数器很好地增加。如果您考虑具有毫秒分辨率的递增计数器的ElapsedTime
字段,我需要的是UI线程上的作业,以便在相应的调用elapsedTimeTextField.setText("00:00:00.001")
之前1ms调用elapsedTimeTextField.setText("00:00:00.002")
。我还需要让UI线程在这两个调用之间执行更重要的工作。
构造代码来执行此操作非常繁琐,并且导致我们的许多控制器类创建的线程只是循环代码类似于:
Thread worker = new Thread(this::doUpdates);
worker.start();
//...
private void doUpdates(){
while(true){
String computedTime = computeTimeToDisplay();
runLaterOnUI(() -> textField.setText(computedTime));
sleep(DUTY_CYCLE_DOWNTIME);
}
}
虽然完成了这项工作,但它不利,因为:
sleep()
s computeTimeToDisplay()
方法中引发异常(或者为此,在runLaterOnUI
调用或sleep()
调用中)文本字段将不再更新。 我已经单独地解决了这些问题,但我没有任何明显和可重复使用的习惯来解决这三个问题。
我怀疑 Future
,Task
,Executor
,ServiceExecutor
等类(java.util.concurrent
中的类不是锁或集合的包可以帮助我实现这个目标,但我不确定如何使用它们。
有人可以建议阅读一些文档和一些可以帮助我追求这些目标的习语吗?对于这种并发工作,是否存在一个同意的成语 - 不涉及匿名类并且包含最少的样板?
答案 0 :(得分:1)
我建议使用核心池大小为1
的{{3}},并选择线程优先级为Thread.NORM_PRIORITY + 1
(使用ScheduledThreadPoolExecutor创建ThreadFactory
UI线程的高于标准优先级的权限 - 这将允许您使用ScheduledThreadPoolExecutor#scheduleAtFixedRate
计划诸如计数器增量之类的任务。不要在此执行程序上执行除UI任务之外的任何操作 - 在具有标准优先级的单独ThreadPoolExecutor
上执行CPU任务;如果你有例如然后,16个逻辑核心创建一个具有16个核心线程的ThreadPoolExecutor
,以便在UI线程空闲时充分利用您的计算机,并让虚拟机负责确保UI线程在其执行时执行其作业。应该是。
答案 1 :(得分:1)
你的问题是多方面的,我不会假装我理解所有这些。这个答案只涉及问题的一部分。
它没有任何退避:如果UI线程充满了作业,这段代码将加剧问题。某种重新排队方案,其中停机时间考虑了作业的延迟和某种类型的硬编码睡眠是优选的,因为这意味着如果UI工作被淹没,我们不会要求它过度地工作。
内置的java.util.concurrent类(例如Task,Service和ScheduledService)包括将非UI线程中的消息更新发送到UI线程的设施,其方式不会泛滥UI线程。你可以直接使用这些课程(这似乎是可取的,但也许这种感觉对我很幼稚,因为我不完全理解你的要求)。或者,如果您没有直接使用java.util.concurrent,则可以在代码中实现类似的自定义工具。
以下是Task实现的相关代码:
/**
* Used to send message updates in a thread-safe manner from the subclass
* to the FX application thread. AtomicReference is used so as to coalesce
* updates such that we don't flood the event queue.
*/
private AtomicReference<String> messageUpdate = new AtomicReference<>();
private final StringProperty message = new SimpleStringProperty(this, "message", "");
/**
* Updates the <code>message</code> property. 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.
* <p>
* <em>This method is safe to be called from any thread.</em>
* </p>
*
* @param message the new message
*/
protected void updateMessage(String message) {
if (isFxApplicationThread()) {
this.message.set(message);
} else {
// As with the workDone, it might be that the background thread
// will update this message quite frequently, and we need
// to throttle the updates so as not to completely clobber
// the event dispatching system.
if (messageUpdate.getAndSet(message) == null) {
runLater(new Runnable() {
@Override public void run() {
final String message = messageUpdate.getAndSet(null);
Task.this.message.set(message);
}
});
}
}
}
该代码的工作原理是确保仅在UI处理(即呈现)上次更新时才进行runLater
调用。
JavaFX 8系统内部在脉冲系统上运行。除非在UI线程或一般系统减速上存在异常耗时的操作,否则每个脉冲通常每秒发生60次,或大约每16-17毫秒发生一次。
你提到以下内容:
我需要的是在UI线程上调用elapsedTimeTextField.setText(&#34; 00:00:00.001&#34;)在相应的调用elatedTimeTextField.setText(&#34; 00: 00:00.002&#34)
。
但是,您可以从JavaFX体系结构描述中看到,每秒更新文本超过60次是没有意义的,因为永远不会呈现其他更新。来自Task的上述示例代码通过确保UI更新请求仅在UI更新线程实际反映UI中的新值时发布来处理此事。
一些一般性建议
这只是建议,它并没有直接解决你的问题,根据你的意愿去做,有些甚至可能与你的情况或问题无关。