JavaFX 中的复杂并发:使用来自多个工作线程的 ObservableLists 和 Properties
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18530493/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Complex concurrency in JavaFX: using ObservableLists and Properties from multiple worker threads
提问by Coder Nr 23
I have multiple worker threads, and a JavaFX GUI which is reporting on what is happening in these threads.
我有多个工作线程和一个 JavaFX GUI,它报告这些线程中发生的事情。
There is a lot of data shared between threads and it needs to be visualized. So I'm using ObservableList's and Property's to be able to easily show the data in JavaFX.
线程之间有很多数据共享,需要可视化。所以我使用 ObservableList 和 Property 来能够轻松地在 JavaFX 中显示数据。
I've made a small example app to show something similar to what happens in my application. It has 2 lists and the worker thread moves data from one list to the other. A status String is kept up to date. Full example code can be found at http://codetidy.com/6569/(this code will crash, see later)
我制作了一个小示例应用程序来展示类似于我的应用程序中发生的事情。它有 2 个列表,工作线程将数据从一个列表移动到另一个列表。状态字符串保持最新。完整的示例代码可以在http://codetidy.com/6569/找到 (这段代码会崩溃,见下文)
Here are the shared ObservableList's & Properties:
以下是共享的 ObservableList 和属性:
private ObservableList<String> newItems;
private ObservableList<String> readyItems;
private StringProperty status;
Here's how they are used in JavaFX:
以下是它们在 JavaFX 中的使用方式:
listViewA.setItems(processor.getNewItems());
listViewB.setItems(processor.getReadyItems());
statusLabel.textProperty().bind(processor.getStatus());
The worker thread updates these lists and properties, but off course, it needs to do this on the JavaFX thread, and that's where things get ugly. This would be the code if I didn't have to update on the JavaFX thread:
工作线程更新这些列表和属性,但当然,它需要在 JavaFX 线程上执行此操作,这就是事情变得丑陋的地方。如果我不必在 JavaFX 线程上更新,这将是代码:
Runnable newItemAdder = new Runnable() {
@Override
public void run() {
while(true) {
synchronized (newItems) {
String newItem = checkForNewItem(); //slow
if (newItem != null) {
newItems.add(newItem);
newItems.notify();
}
if (newItems.size() >= 5)
status.set("Overload");
else
status.set("OK");
}
synchronized (readyItems) {
if (readyItems.size() > 10)
readyItems.remove(0);
}
try { Thread.sleep(200); } catch (InterruptedException e) { return; }
}
}
};
new Thread(newItemAdder).start();
Runnable worker = new Runnable() {
@Override
public void run() {
while(true) {
List<String> toProcess = new ArrayList<String>();
synchronized (newItems) {
if (newItems.isEmpty())
try { newItems.wait(); } catch (InterruptedException e) { return; }
toProcess.addAll(newItems);
}
for (String item : toProcess) {
String processedItem = processItem(item); //slow
synchronized (readyItems) {
readyItems.add(processedItem);
}
}
}
}
};
new Thread(worker).start();
Off course, some things are easy to solve with Platform.runLater:
当然,有些事情很容易用 Platform.runLater 解决:
Platform.runLater(new Runnable() {
@Override
public void run() {
synchronized (newItems) {
if (newItems.size() >= 5)
status.set("Overload");
else
status.set("OK");
}
}
});
That's fine for properties/lists that I only write to in the task, and only read in the JavaFX GUI. But it gets very complicated to do it for the lists in this example, on which you need to synchronize, read and write. You need add a lot of Platform.runLater and you need to block until the "runLater" task has finished. This results in very complex and hard to read and write code (I managed to get this example running this way, see what I mean: http://codetidy.com/6570/).
这对于我只在任务中写入并且只在 JavaFX GUI 中读取的属性/列表很好。但是对于这个例子中的列表,它变得非常复杂,你需要同步、读取和写入。您需要添加大量 Platform.runLater 并且需要阻塞直到“runLater”任务完成。这导致非常复杂且难以读写的代码(我设法让这个例子以这种方式运行,明白我的意思:http: //codetidy.com/6570/)。
Are there any other ways to get my example working? I'd appreciate any other solution or partial solutions...
还有其他方法可以让我的示例工作吗?我很感激任何其他解决方案或部分解决方案......
采纳答案by jewelsea
Background Info
背景信息
Task javadocincludes numerous concurrency usage patterns for passing data between threads in JavaFX.
任务 javadoc包括许多并发使用模式,用于在 JavaFX 中的线程之间传递数据。
Taskincludes convenience data transfer methods such as updateMessageand can be used instead of your Runnable with the user defined status property.
Task包括便利的数据传输方法,例如updateMessage,并且可以使用用户定义的 status 属性代替您的 Runnable。
When appropriate, consider using a collection structure designed for concurrency, such as a BlockingQueue. An additional advantage is that BlockingQueues can have size limits, which seems like something you want.
在适当的时候,考虑使用专为并发设计的集合结构,例如BlockingQueue。另一个优点是 BlockingQueues 可以有大小限制,这似乎是您想要的。
Some general advice
一些一般性建议
- Be very careful when using mutable observable items in multiple threads. It is easy to inadvertently trigger updates that result in race conditions, updates to the active scene graph off the application thread and other threading issues.
- As much as possible, use immutable datarather than mutable observable items.
- Leverage some of the higher level utilities from the JavaFX concurrencyand java.util.concurrentlibraries.
- Avoid explicit synchronization and notify statements as much as possible.
- Be careful placing synchronization or other potentially blocking statements in code that runs on the JavaFX Application Thread - as you may make your GUI unresponsive.
- Use the JavaFX concurrency utilities only when you need interaction with the JavaFX thread.
- Do a lot of your very complicated multi-threaded processing off of the JavaFX thread using standard Java concurrency utilities. Have a single co-ordinating JavaFX Task for coalescing and controlling UI feedback.
- 在多线程中使用可变的可观察项时要非常小心。很容易无意中触发导致竞争条件的更新、应用程序线程外活动场景图的更新以及其他线程问题。
- 尽可能使用不可变数据而不是可变的可观察项。
- 利用JavaFX 并发和java.util.concurrent库中的一些更高级别的实用程序。
- 尽量避免显式同步和通知语句。
- 在 JavaFX 应用程序线程上运行的代码中放置同步或其他可能阻塞的语句时要小心 - 因为这可能会使您的 GUI 无响应。
- 仅当您需要与 JavaFX 线程交互时才使用 JavaFX 并发实用程序。
- 使用标准 Java 并发实用程序在 JavaFX 线程之外执行许多非常复杂的多线程处理。有一个单一的协调 JavaFX 任务来合并和控制 UI 反馈。
The above are just rules of thumb and don't need to be followed didactically.
以上只是经验法则,不需要在教学上遵循。
Reasonably Complex Threading Samples
合理复杂的线程示例
- A chart rendererthat demonstrates some of the principles above to render 300 charts while still keeping the UI responsive to progress updates and user interaction.
- 一个图表渲染器,演示了上述一些原则,以渲染 300 个图表,同时仍然保持 UI 对进度更新和用户交互的响应。
回答by Coder Nr 23
The original links with the full example and the example solution by jewelsea are dead, so I'll add an answer with a short summary of what I ended up doing.
完整示例的原始链接和jewelsea 的示例解决方案已失效,因此我将添加一个答案,并简要总结我最终所做的事情。
To make this easy, let's assume you start with 1 class that holds your data model (let's call it DataModel
). An instance of this class is used by multiple threads that change it.
为方便起见,我们假设您从保存数据模型的 1 个类开始(我们称之为DataModel
)。此类的实例由更改它的多个线程使用。
Now the problem is that you want to use the data model in javaFX, but you cannot simply change your data model to use Property
and ObservableList
etc. If you do that, the listeners will be called from non-javafx threads, and GUI's bound to them will throw exceptions.
现在的问题是,你要使用JavaFX中的数据模型,但你不能简单地改变你的数据模型来使用Property
和ObservableList
等,如果你这样做,听众将非JavaFX的线程调用和GUI的绑定到这些会抛出异常。
Instead you need to make a seperate data model class for javaFX. This is simply the JavaFX version of the original one (let's call it FXDataModel
). So this version contains the same info, but it uses javaFX Property
and ObservableList
. This way, you can bind your GUI to it.
相反,您需要为 javaFX 创建一个单独的数据模型类。这只是原始版本的 JavaFX 版本(我们称之为FXDataModel
)。所以这个版本包含相同的信息,但它使用 javaFXProperty
和ObservableList
. 这样,您就可以将您的 GUI 绑定到它。
The next step is to periodically update a FXDataModel
instance using the DataModel
instance.
To do this, you add an update(DataModel dataModel)
method to FXDataModel
, which copies the data from the original data model into the FXDataModel
instance. This update function must always be called on the javaFX thread. Finally, all you need to do is periodically call that update function.
下一步是FXDataModel
使用DataModel
实例定期更新实例。为此,您向 中添加了一个update(DataModel dataModel)
方法FXDataModel
,该方法将数据从原始数据模型复制到FXDataModel
实例中。必须始终在 javaFX 线程上调用此更新函数。最后,您需要做的就是定期调用该更新函数。
In my real scenario, I call the update function every 200 ms, and that is more than enough to be able to show a live view of the data model in the GUI. (Things get more complex if you want to more than a view of the data model, and you want to change things from the GUI, but that is not something I needed to do)
在我的真实场景中,我每 200 毫秒调用一次更新函数,这足以在 GUI 中显示数据模型的实时视图。(如果您想要的不仅仅是数据模型的视图,并且您想要从 GUI 更改内容,事情会变得更加复杂,但这不是我需要做的事情)