我已经阅读了几个小时的关于JavaFX的不同多线程技术的文章,但似乎找不到我想要的东西。正在使用的应用程序是“ Messenger”,它是一个较大的应用程序的一部分,可为游戏提供交易市场。
我遇到问题的过程的细分:
问题:
现在,当我需要打开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;
联系卖方按钮逻辑:如果未打开Messenger,则创建它并传递给静态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循环和使用触发的布尔值很草率..但这是通常的做法吗?
答案 0 :(得分:0)
请勿尝试使用安排聊天检索的相同方法来处理打开的聊天。而是创建一个处理程序队列,在聊天准备好后立即执行。
简化示例
public class ChatManager {
private final Map<String, Chat> openChats = new HashMap<>();
// only call from application thread
public void openChat(String user, Consumer<Chat> chatReadyHandler) {
Chat chat = openChats.computeIfAbsent(user, this::createNewChat);
chat.addReadyHandler(chatReadyHandler);
}
private Chat createNewChat(String user) {
return new Chat(user);
}
public class Chat {
// list keeping track of handlers any used for synchronisation
private final ArrayList<Consumer<Chat>> readyHandlers = new ArrayList<>(1);
private boolean ready = false;
private final String user;
public String getUser() {
return user;
}
private void addReadyHandler(Consumer<Chat> chatReadyHandler) {
synchronized (readyHandlers) {
// if already ready, immediately execute, otherwise enqueue
if (ready) {
chatReadyHandler.accept(this);
} else {
readyHandlers.add(chatReadyHandler);
}
}
}
private void chatReady() {
synchronized (readyHandlers) {
ready = true;
}
// execute all handlers on the application thread
Platform.runLater(() -> {
synchronized (readyHandlers) {
for (Consumer<Chat> readyHandler : readyHandlers) {
readyHandler.accept(this);
}
readyHandlers.clear();
readyHandlers.trimToSize();
}
});
}
private Chat(String user) {
this.user = user;
new Thread(() -> {
try {
Thread.sleep(10000); // simulate time required to acquire chat
} catch (InterruptedException ex) {
}
chatReady();
}).start();
}
}
}
在Chat
中输入用户名后,按 Enter 时,以下代码将与用户创建TextField
,并向TextArea
打印一条消息聊天准备好后。
@Override
public void start(Stage primaryStage) throws Exception {
ChatManager worker = new ChatManager();
TextField userName = new TextField();
TextArea textArea = new TextArea();
textArea.setEditable(false);
userName.setOnAction(evt -> {
String user = userName.getText();
userName.clear();
textArea.appendText("opening chat for " + user + "\n");
worker.openChat(user, chat -> textArea.appendText("chat for " + chat.getUser() + " ready\n"));
});
Scene scene = new Scene(new VBox(10, userName, textArea));
primaryStage.setScene(scene);
primaryStage.show();
}