簡體   English   中英

如何將消息從 TimerTask 傳遞到主線程?

[英]How to pass a message from TimerTask to main thread?

我有一個主客戶端,它為每個對等端保留后台計時器。 這些計時器在后台線程中運行,並計划在 30 秒(超時時間)內執行將相應對等點標記為離線的任務。 執行此操作的代碼塊是:

  public void startTimer() {
    timer = new Timer();
    timer.schedule(new TimerTask() {
      public void run() {
        status = false;
        System.out.println("Setting " + address.toString() + " status to offline");
        // need to send failure message somehow
        thread.sendMessage();
      }
    }, 5*1000);
  }

然后,在主程序中,我需要一些方法來檢測上述計時器任務何時運行,以便主客戶端可以向所有其他對等點發送failure消息,如下所示:

while (true)
  if (msgFromThreadReceived)
     notifyPeers();

我將如何使用 TimerTask 完成此操作? 據我了解,計時器在一個單獨的線程中運行,我想以某種方式將消息傳遞給主線程以通知主線程任務已經運行。

我會讓 class 處理對等點的計時器,並發隊列並在對等點脫機時將消息放入隊列中。 然后“主”線程可以以事件驅動的方式輪詢隊列,接收和處理消息。

請注意,這個“主”線程不能是 GUI 框架的事件分發線程。 如果當主線程收到消息時,GUI 中需要更新某些內容,它可以在收到消息后調用事件派發線程上的另一段代碼。

隊列的兩個不錯的選擇是ConcurrentLinkedQueue如果隊列應該是無界的(定時器線程可以在主線程拾取它們之前將任意數量的消息放入隊列),或者LinkedBlockingQueue如果應該限制隊列的大小隊列,如果它變得太大,計時器線程必須等待才能在其上放置另一條消息(這稱為背壓,在分布式並發系統中可能很重要,但在您的情況下可能不相關)。

這里的想法是實現 Actor Model (qv) 的一個版本,其中線程(actors)之間不共享任何內容,任何需要發送的數據(應該是不可變的)在它們之間傳遞。 每個參與者都有一個收件箱,它可以在其中接收消息並根據消息采取行動。 只是,您的計時器線程可能不需要收件箱,如果它們將所有數據作為構造函數的參數並且在啟動后不需要從主線程接收任何消息。

public record PeerDownMessage(String peerName, int errorCode) {
}

public class PeerWatcher {
    private final Peer peer;
    private final BlockingQueue<PeerDownMessage> queue;

    public PeerWatcher(Peer peer, BlockingQueue<PeerDownMessage> queue) {
        this.peer = Objects.requireNonNull(peer);
        this.queue = Objects.requireNonNull(queue);
    }

    public void startTimer() {
        // . . .
            // time to send failure message
            queue.put(new PeerDownMessage(peer.getName(), error));            
        // . . .
    }
}

public class Main {
    public void eventLoop(List<Peer> peers) {
        LinkedBlockingQueue<PeerDownMessage> inbox =
            new LinkedBlockingQueue<>();
        for (Peer peer : peers) {
            PeerWatcher watcher = new PeerWatcher(peer, inbox);
            watcher.startTimer();
        }
   
        while (true) {
            PeerDownMessage message = inbox.take();
            SwingWorker.invokeLater(() {
                // suppose there is a map of labels for each peer
                JLabel label = labels.get(message.peerName());
                label.setText(message.peerName() +
                    " failed with error " + message.errorCode());
            });
        }
    }    
}

請注意,要更新 GUI,我們會在另一個線程 Swing 事件調度線程上執行該操作,該線程必須與我們的主線程不同。

您可以使用大型、復雜的框架來實現 actor model,但其核心是:線程之間不共享任何內容,因此您永遠不需要同步或使任何東西變得不穩定,actor 需要的任何東西都可以作為參數接收到它的構造函數或通過它的收件箱(在這個例子中,只有主線程有一個收件箱,因為工作線程一旦啟動就不需要接收任何東西),最好讓所有東西都是不可變的。 我使用record而不是 class 作為消息,但您可以使用常規的class 只需將字段設置為 final,在構造函數中設置它們,並保證它們不能是 null,如 PeerWatcher 中的PeerWatcher

我說主線程可以輪詢“隊列”,這意味着可能有多個隊列,但在這種情況下,它們都發送相同類型的消息,並且它們在消息正文中識別消息是針對哪個對等點的。 所以我只是給每個觀察者一個對主線程同一個收件箱的引用。 這可能是最好的。 一個演員應該只有一個收件箱; 如果它需要做多件事,它可能應該是多個參與者(這是 Erlang 方式,這就是我從中獲得靈感的地方)。

但是如果你真的需要有多個隊列,main 可以像這樣輪詢它們:

while (true) {
    for (LinkedBlockingQueue<PeerDownMessage> queue : queues) {
        if (queue.peek() != null) {
            PeerDownMessage message = queue.take();
            handleMessageHowever(message);
        }
    }
}

但這是你不需要的很多額外的東西。 堅持每個參與者一個收件箱隊列,然后輪詢收件箱以獲取要處理的消息很簡單。

我最初寫這個是為了使用 ConcurrentLinkedQueue,但我使用puttake ,它們是 BlockingQueue 的方法。 我只是將其更改為使用 LinkedBlockingQueue,但如果您更喜歡 ConcurrentLinkedQueue,則可以使用addpoll ,但如果進一步考慮,我真的會推薦 BlockingQueue,因為它的take()方法非常簡單; 它可以讓您在等待下一個可用項目時輕松阻塞,而不是忙着等待。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM