等到布尔值更改而没有阻塞主JavaFX线程?

时间:2018-07-28 03:47:44

标签: java multithreading javafx concurrency blocking

我已经阅读了几个小时的关于JavaFX的不同多线程技术的文章,但似乎找不到我想要的东西。正在使用的应用程序是“ Messenger”,它是一个较大的应用程序的一部分,可为游戏提供交易市场。

我遇到问题的过程的细分:

  • 显示带有“联系卖家”按钮的窗口
  • 用户单击“联系卖家”,并显示Messenger窗口。
  • 使用主窗口中的卖方名称,Messenger应该检查是否已经存在使用该名称的聊天
  • 如果该聊天已经存在,请在Messenger的ListView中获取该聊天的索引,然后选择该聊天,以便在Messenger的文本区域中填充相应的消息
  • 如果聊天不存在,请创建一个

问题:

  • 聊天记录存储在服务器端
  • 客户端之间的消息在从一个用户处理到另一个用户时被存储在服务器上的消息“缓存”中(它们被分类到各自的聊天记录中,插入到聊天记录中,并在客户端断开连接时被推送到数据库中)< / li>
  • 在客户端上收到的消息存储在各自的聊天记录中
  • 打开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;

联系卖方按钮逻辑:如果未打开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循环和使用触发的布尔值很草率..但这是通常的做法吗?

1 个答案:

答案 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();
}