简体   繁体   English

如何将消息从 TimerTask 传递到主线程?

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

I have a main client which keeps background timers for each peer.我有一个主客户端,它为每个对等端保留后台计时器。 These timers run in a background thread, and in 30s (the timeout period) are scheduled to perform the task of marking the respective peer as offline.这些计时器在后台线程中运行,并计划在 30 秒(超时时间)内执行将相应对等点标记为离线的任务。 The block of code to do this is:执行此操作的代码块是:

  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);
  }

Then, in the main program, I need some way to detect when the above timer task has been run, so that the main client can then send a failure message to all other peers, something like:然后,在主程序中,我需要一些方法来检测上述计时器任务何时运行,以便主客户端可以向所有其他对等点发送failure消息,如下所示:

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

How would I be able to accomplish this with TimerTask?我将如何使用 TimerTask 完成此操作? As I understand, the timer is running in a separate thread, and I want to somehow pass a message to the main thread to notify the main thread that the task has been run.据我了解,计时器在一个单独的线程中运行,我想以某种方式将消息传递给主线程以通知主线程任务已经运行。

I would have the class that handles the timers for the peers take a concurrent queue and place a message in the queue when the peer goes offline.我会让 class 处理对等点的计时器,并发队列并在对等点脱机时将消息放入队列中。 Then the "main" thread can poll the queue(s) in an event-driven way, receiving and processing the messages.然后“主”线程可以以事件驱动的方式轮询队列,接收和处理消息。

Please note that this "main" thread MUST NOT be the event dispatch thread of a GUI framework.请注意,这个“主”线程不能是 GUI 框架的事件分发线程。 If there is something that needs to be updated in the GUI when the main thread receives the message, it can invoke another piece of code on the event dispatch thread upon reception of the message.如果当主线程收到消息时,GUI 中需要更新某些内容,它可以在收到消息后调用事件派发线程上的另一段代码。

Two good choices for the queue would be ConcurrentLinkedQueue if the queue should be unbounded (the timer threads can put any number of messages in the queue before the main thread picks them up), or LinkedBlockingQueue if there should be a limit on the size of the queue, and if it gets too large, the timer threads have to wait before they can put another message on it (this is called backpressure, and can be important in distributed, concurrent systems, but may not be relevant in your case).队列的两个不错的选择是ConcurrentLinkedQueue如果队列应该是无界的(定时器线程可以在主线程拾取它们之前将任意数量的消息放入队列),或者LinkedBlockingQueue如果应该限制队列的大小队列,如果它变得太大,计时器线程必须等待才能在其上放置另一条消息(这称为背压,在分布式并发系统中可能很重要,但在您的情况下可能不相关)。

The idea here is to implement a version of the Actor Model (qv), in which nothing is shared between threads (actors), and any data that needs to be sent (which should be immutable) is passed between them.这里的想法是实现 Actor Model (qv) 的一个版本,其中线程(actors)之间不共享任何内容,任何需要发送的数据(应该是不可变的)在它们之间传递。 Each actor has an inbox in which it can receive messages and it acts upon them.每个参与者都有一个收件箱,它可以在其中接收消息并根据消息采取行动。 Only, your timer threads probably don't need inboxes, if they take all their data as parameters to the constructor and don't need to receive any messages from the main thread after they're started.只是,您的计时器线程可能不需要收件箱,如果它们将所有数据作为构造函数的参数并且在启动后不需要从主线程接收任何消息。

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

Notice that to update the GUI, we cause that action to be performed on yet another thread, the Swing Event Dispatch Thread, which must be different from our main thread.请注意,要更新 GUI,我们会在另一个线程 Swing 事件调度线程上执行该操作,该线程必须与我们的主线程不同。

There are big, complex frameworks you can use to implement the actor model, but the heart of it is this: nothing is shared between threads, so you never need to synchronize or make anything volatile, anything an actor needs it either receives as a parameter to its constructor or via its inbox (in this example, only the main thread has an inbox since the worker threads don't need to receive anything once they are started), and it is best to make everything immutable.您可以使用大型、复杂的框架来实现 actor model,但其核心是:线程之间不共享任何内容,因此您永远不需要同步或使任何东西变得不稳定,actor 需要的任何东西都可以作为参数接收到它的构造函数或通过它的收件箱(在这个例子中,只有主线程有一个收件箱,因为工作线程一旦启动就不需要接收任何东西),最好让所有东西都是不可变的。 I used a record instead of a class for the message, but you could use a regular class .我使用record而不是 class 作为消息,但您可以使用常规的class Just make the fields final, set them in the constructor, and guarantee they can't be null, as in the PeerWatcher class.只需将字段设置为 final,在构造函数中设置它们,并保证它们不能是 null,如 PeerWatcher 中的PeerWatcher

I said the main thread can poll the "queue(s)," implying there could be more than one, but in this case they all send the same type of message, and they identify which peer the message is for in the message body.我说主线程可以轮询“队列”,这意味着可能有多个队列,但在这种情况下,它们都发送相同类型的消息,并且它们在消息正文中识别消息是针对哪个对等点的。 So I just gave every watcher a reference to the same inbox for the main thread.所以我只是给每个观察者一个对主线程同一个收件箱的引用。 That's probably best.这可能是最好的。 An actor should just have one inbox;一个演员应该只有一个收件箱; if it needs to do multiple things, it should probably be multiple actors (that's the Erlang way, and that's where I've taken the inspiration for this from).如果它需要做多件事,它可能应该是多个参与者(这是 Erlang 方式,这就是我从中获得灵感的地方)。

But if you really needed to have multiple queues, main could poll them like so:但是如果你真的需要有多个队列,main 可以像这样轮询它们:

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

But that's a lot of extra stuff you don't need.但这是你不需要的很多额外的东西。 Stick to one inbox queue per actor, and then polling the inbox for messages to process is simple.坚持每个参与者一个收件箱队列,然后轮询收件箱以获取要处理的消息很简单。

I initially wrote this to use ConcurrentLinkedQueue but I used put and take which are methods of BlockingQueue.我最初写这个是为了使用 ConcurrentLinkedQueue,但我使用puttake ,它们是 BlockingQueue 的方法。 I just changed it to use LinkedBlockingQueue but if you prefer ConcurrentLinkedQueue, you can use add and poll but on further consideration, I would really recommend BlockingQueue for the simplicity of its take() method;我只是将其更改为使用 LinkedBlockingQueue,但如果您更喜欢 ConcurrentLinkedQueue,则可以使用addpoll ,但如果进一步考虑,我真的会推荐 BlockingQueue,因为它的take()方法非常简单; it lets you easily block while waiting for the next available item instead of busy waiting.它可以让您在等待下一个可用项目时轻松阻塞,而不是忙着等待。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM