【发布时间】:2018-07-28 03:47:44
【问题描述】:
我已经阅读了几个小时关于 JavaFX 的不同多线程技术的文章,但似乎找不到我想要的东西。正在开发的应用程序是“Messenger”,它是为游戏提供交易市场的更大应用程序的一部分。
我遇到问题的流程细分:
- 显示带有“联系卖家”按钮的窗口
- 用户点击“联系卖家”,应显示 Messenger 窗口
- 使用主窗口中的卖家名称,Messenger 应检查是否已经存在使用该名称的聊天
- 如果聊天已经存在,则在 Messenger 的 ListView 中获取该聊天的索引,然后选择该聊天,以便在 Messenger 的文本区域中填充相应的消息
- 如果聊天不存在,创建一个
问题:
- 聊天存储在服务器端
- 客户端之间的消息在从一个用户到另一个用户处理时存储在服务器上的消息“缓存”中(它们被分类到各自的聊天中,插入到聊天中,并在客户端断开连接时推送到数据库)
- 在客户端收到的消息本地存储在各自的聊天中
- 当 Messenger 打开时,它会请求登录用户的聊天列表
- 服务器发送一个聊天的 ArrayList,客户端收到后用这些对象构建 Messenger 的 ListView
现在,当我需要打开 Messenger 以“联系卖家”时,我需要确保与服务器的同步已完成。如果不这样做,我将无法正确检查是否已经存在使用该名称的聊天,因为我没有最新的列表。
处理传入服务器消息的“RequestWorker”线程不在 JavaFX 线程上。
RequestWorker“获取”当前打开的 Messenger 实例,并使用新接收的聊天列表填充 ListView。 (这需要在 JavaFX 线程上发生,因为我在 Messenger GUI 中工作)
我要做的是在 Messenger 在显示时启动同步时将静态 AtomicBoolean syncInProgress 设置为 true。当 RequestWorker 从服务器接收到最新列表并完成填充 Messengers ListView 时,它将 syncInProgress 设置为 false。
与打开 Messenger 并检查是否存在聊天相比,同步所需的时间更长。这样做,它还没有在 ListView 中填充任何项目,并且该方法无效。
调用 while 循环等待布尔值改变,阻塞 JavaFX 线程,这意味着 RequestWorker 无法在 JavaFX 线程中做它需要做的事情。
如何持续检查此变量是否设置为 false,然后在正确填充 ListView 后继续“联系卖家”?
联系卖家方法:这里的while循环导致JavaFX线程阻塞,因此无法使RequestWorker正确填充ListView。
public static void contactSeller(Messenger messenger, String destination, String itemName)
{
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete to check the latest chat list for an existing chat
}
if (messenger.chatExists(destination))
{
// Chat exists, select the chat for the user
for (Chat openChat : messenger.chatListView.getItems())
{
if (openChat.getName(TarkovTrader.username).equals(destination))
{
messenger.chatListView.getSelectionModel().select(openChat);
messenger.unpackChatMessages(openChat.getMessages());
break;
}
}
}
else
{
messenger.buildNewChat(destination);
}
messenger.chatInput.setText("Hey " + destination + ". Interested in your '" + itemName + "'.");
messenger.chatInput.setOnMouseClicked(e -> messenger.chatInput.clear());
}
RequestWorker 进程聊天块:
switch(receivedFromServer)
case "chatlist":
// Client requested a chat list, results were returned from the server, and now we need to populate the messenger list
ChatListForm chatlistform = (ChatListForm)processedRequest;
if (Messenger.isOpen)
{
FutureTask<Void> updateChatList = new FutureTask(() -> {
Messenger tempMessenger = trader.getMessenger();
int currentIndex = tempMessenger.chatListView.getSelectionModel().getSelectedIndex();
tempMessenger.populate(chatlistform.getChatList());
tempMessenger.chatListView.getSelectionModel().select(currentIndex);
}, null);
Platform.runLater(updateChatList); // RequestWorker needs access to the JavaFX application thread
try {
updateChatList.get(); // Wait until the ListView has been populated before setting 'syncInProgress' to false again
}
catch (InterruptedException e) {
Alert.display(null, "Sync interrupted.");
}
catch (ExecutionException e) {
Alert.display(null, "Sync failed.");
}
TarkovTrader.syncInProgress.compareAndSet(true, false); // The value of syncInProgress should be true, change to false. Sync complete
}
else
{
Platform.runLater(() -> Alert.display(null, "New chat received."));
TarkovTrader.syncInProgress.compareAndSet(true, false);
}
break;
联系卖家按钮逻辑:如果信使未打开,则创建它并传递给静态contactSeller 方法以使用。
contactButton.setOnAction(e -> {
Messenger messenger;
if (Messenger.isOpen)
{
// Get messenger
messenger = trader.getMessenger();
}
else
{
messenger = new Messenger(worker);
messenger.display();
trader.setMessenger(messenger);
}
Messenger.contactSeller(messenger, item.getUsername(), item.getName());
itemdisplay.close();
});
编辑: 部分使用 Slaw 的想法(AtomicBoolean 仍在使用,因为没有它我不知道该怎么做),这就是我想出的......
public static void contactSeller(Messenger messenger, String destination, String itemName)
{
Task<Void> waitForSync = new Task<Void>() {
@Override
public Void call()
{
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete
}
return null;
}
};
waitForSync.setOnSucceeded(e -> {
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete to check the latest chat list for an existing chat
}
if (messenger.chatExists(destination))
{
// Chat exists, select the chat for the user
for (Chat openChat : messenger.chatListView.getItems())
{
if (openChat.getName(TarkovTrader.username).equals(destination))
{
messenger.chatListView.getSelectionModel().select(openChat);
messenger.unpackChatMessages(openChat.getMessages());
break;
}
}
}
else
{
messenger.buildNewChat(destination);
}
messenger.chatInput.setText("Hey " + destination + ". Interested in your '" + itemName + "'.");
messenger.chatInput.setOnMouseClicked(me -> messenger.chatInput.clear());
});
Thread t = new Thread(waitForSync);
t.setDaemon(true);
t.start();
}
这确实有效,但这似乎不是一个很好的解决方案。做这样的事情可以吗,还是有比这更好的方法?我觉得 while 循环和使用触发的布尔值很草率..但这是常见的做法吗?
【问题讨论】:
-
您提到阅读 JavaFX 中不同的多线程选项,所以也许您已经考虑过这一点,但是...您为什么不使用
javafx.concurrent.Task?一旦Task成功完成,您将使用onSucceededproperty 在FX 线程上执行代码,而不是使用AtomicBoolean标志并忙于等待。 -
@Slaw 感谢您的回复!我一直在考虑如何实现这一点,但我不确定如何使其工作,因为在执行同步后不一定总是调用“contactSeller”。你能给我一个关于如何实施的想法吗?
-
@Slaw 我在底部添加了一个代码 sn-p。我知道这不是你的建议,但我不确定如何在没有布尔播放某种因素的情况下实现这一点。布尔值是我知道客户端从服务器收到列表的唯一方法
标签: java multithreading javafx concurrency blocking