如何使用 SwingWorker 模拟缓冲外围设备? [英] How do I simulate a buffered peripheral device with SwingWorker?

查看:29
本文介绍了如何使用 SwingWorker 模拟缓冲外围设备?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将此练习用作教学工具,以帮助我掌握一些 Java GUI 编程概念.我正在寻找的是一般的理解,而不是对一个特定问题的详细解决方案.我希望编写这个正确"的代码会教我很多关于如何处理未来多线程问题的知识.如果这对于这个论坛来说太笼统了,它可能属于程序员吗?

I'm using this exercise as a pedagogical tool to help me burn in some Java GUI programming concepts. What I'm looking for is a general understanding, rather than a detailed solution to one specific problem. I expect that coding this "right" will teach me a lot about how to approach future multi-threaded problems. If this is too general for this forum, possibly it belongs in Programmers?

我正在模拟读卡器.它有一个 GUI,允许我们将卡片装入料斗并按 Start 等,但它的主要客户端"是 CPU,在单独的线程上运行并请求卡片.

I'm simulating a card reader. It has a GUI, allowing us to load cards into the hopper and press Start and so forth, but its main "client" is the CPU, running on a separate thread and requesting cards.

读卡器维护一个缓冲区.如果收到卡片请求并且缓冲区为空,则读卡器必须从漏斗中读取卡片(这需要 1/4 秒,即 1962 年).读卡器读入缓冲区后,读卡器将缓冲区发送给CPU,并在下一个请求之前立即启动另一个缓冲区加载操作.

The card reader maintains a single buffer. If a card request comes in and the buffer is empty, the card reader must read a card from the hopper (which takes 1/4 of a second, this being 1962). After the card has been read into the buffer, the card reader sends the buffer to the CPU, and immediately initiates another buffer-loading operation, in advance of the next request.

如果不仅缓冲区是空的,而且漏斗中没有卡片,那么我们必须等到操作员在漏斗中放置一副牌并按下开始"(这总是会启动缓冲加载操作).

If not only the buffer is empty but there are no cards in the hopper, then we must wait until the operator has placed a deck in the hopper and pressed Start (which always initiates a buffer-load operation).

在我的实现中,卡片请求以 invokeLater() Runnables 在 EDT 上排队的形式发送到读卡器.在 myRunnable.run() 时间,或者缓冲区将可用(在这种情况下我们可以将它发送到 CPU 并启动另一个缓冲区加载操作),或者缓冲区将为空.如果它是空的怎么办?

In my implementation, card requests are sent to the card reader in the form of invokeLater() Runnables being queued on the EDT. At myRunnable.run() time, either a buffer will be available (in which case we can send it to the CPU and kick off another buffer-load operation), or the buffer will be empty. What if it's empty?

两种可能:(a) 已经有一个正在运行的缓冲加载操作,或者 (b) 卡片槽是空的(或尚未启动).在任何一种情况下,让 EDT 等待都是不可接受的.工作(和等待)必须在后台线程上完成.

Two possibilities: (a) there's already a buffer-load operation in flight, or (b) the card hopper is empty (or hasn't been started). In either case, it's not acceptable to keep the EDT waiting. The work (and the waiting) must be done on a background thread.

为了简单起见,我尝试生成一个 SwingWorker 来响应每个卡片请求,而不管缓冲区的状态如何.伪代码是:

For the sake of simplicity, I tried spawning a SwingWorker in response to every card request, regardless of the status of the buffer. The pseudocode was:

SwingWorker worker = new SwingWorker<Void, Void>() {
    public Void doInBackground() throws Exception {
        if (buffer.isEmpty()) {
            /*
             * fill() takes 1/4 second (simulated by Thread.sleep)
             * or possibly minutes if we need to have another 
             * card deck mounted by operator.
             */
            buffer.fill();
        }
        Card card = buffer.get(); // empties buffer
        /*
         * Send card to CPU
         */
        CPU.sendMessage(card); // <== (A) put card in msg queue
        /* 
         * Possible race window here!!
         */
        buffer.fill(); //         <== (B) pre-fetch next card
        return null;
    }
};
worker.execute();

这产生了一些奇怪的计时效果 - 我怀疑是由于 buffer.fill() 竞争可能发生如下:如果在 (A) 和 (B) 之间,CPU 收到卡,发送了另一个请求,并代表它产生了另一个 SwingWorker 线程,那么可能有两个线程同时尝试填充缓冲区.[删除 (B) 处的预取调用解决了这个问题.]

This produced some odd timing effects - due, I suspect, to a buffer.fill() race that could occur as follows: if, between (A) and (B), the CPU received the card, sent a request for another one, and had another SwingWorker thread spawned on its behalf, then there might be two threads simultaneously trying to fill the buffer. [Removing the pre-fetch call at (B) solved that.]

所以我认为为每次读取生成一个 SwingWorker 线程是错误的.卡片的缓冲和发送必须在单个线程中进行序列化.该线程必须尝试预取缓冲区,并且必须能够在我们用完卡片并且必须等待更多卡片放入漏斗时等待并恢复.我怀疑 SwingWorker 具有处理此问题的长时间运行的后台线程所需的条件,但我还没有做到.

So I think spawning a SwingWorker thread for every read is wrong. The buffering and sending of cards must be serialized in a single thread. That thread must attempt to pre-fetch a buffer, and must be able to wait and resume if we run out of cards and have to wait for more to be placed in the hopper. I suspect that SwingWorker has what is required to be a long-running background thread to handle this, but I'm not quite there yet.

假设一个 SwingWorker 线程是要走的路,我该如何实现这一点,消除 EDT 的延迟,允许线程阻塞等待漏斗重新填充,并处理缓冲区填充是在另一张卡片之前还是之后完成的不确定性请求到达?

Assuming a SwingWorker thread is the way to go, how might I implement this, eliminating delay on the EDT, allowing the thread to block awaiting a hopper refill, and handling the uncertainty of whether buffer-filling completes before or after another card request arrives?

我从另一个线程得到了一个答案,我将在这里回顾一下:

I got an answer from another thread and will recap it here:

建议我在开始时创建一个 ExecutorService newSingleThreadExecutor() 一次,而不是使用 SwingWorker 线程,并使用 GUI 将冗长的方法排入队列execute(Runnable foo),如下(这段代码在EDT中运行):

Instead of using a SwingWorker thread, it was recommended I create an ExecutorService newSingleThreadExecutor() once, at the beginning, and have the GUI enqueue lengthy methods on it using execute(Runnable foo), as follows (this code runs in the EDT):

private ExecutorService executorService;
::
/*
 * In constructor: create the thread
 */
executorService = Executors.newSingleThreadExecutor();
::
/*
 * When EDT receives a request for a card it calls readCard(),
 * which queues the work out to the *single* thread.
 */
public void readCard() throws Exception {
    executorService.execute(new Runnable() {
        public void run() {
            if (buffer.isEmpty()) {
                /*
                 * fill() takes 1/4 second (simulated by Thread.sleep)
                 * or possibly minutes if we need to have another 
                 * card deck mounted by operator.
                 */
                buffer.fill();
            }
            Card card = buffer.get(); // empties buffer
            /*
             * Send card to CPU
             */
            CPU.sendMessage(card); // <== (A) put card in msg queue
            /* 
             * No race!  Next request will run on same thread, after us.
             */
            buffer.fill(); //         <== (B) pre-fetch next card
            return;
        }
    });
}

这与 SwingWorker 之间的主要区别在于,这确保只有一个工作线程.

The main difference between this and SwingWorker is that this ensures there's only one worker thread.

推荐答案

我附加到原始问题的答案"中缺少一件事:

There was one thing missing from the "answer" I had appended to the original question:

我通过单线程执行器将耗时的工作(不过是用于教学目的的 Thread.sleep())移交给后台线程.然而,出现了一个问题,因为后台线程通过轮询()作为 Swing 组件的数据模型的 List 来读取卡片",并引发大量 AWT 数组索引超出范围异常.在 EDT 和我的后台线程同步访问 List 的几次徒劳尝试之后,我放弃并包装命令以 poll() List 并在一个小的 Runnable() 中更新 GUI,并使用 invokeAndWait() 导致它们在我的后台任务等待时在 EDT 上运行.

I was handing off the time-consuming work (nothing more than a Thread.sleep() for pedagogical purposes) to a background thread, via a Single Thread Executor. A problem arose, however, because the background thread was "reading a card" by poll()ing the List that was serving as the data model for a Swing component, and raising lots of AWT array index out of range exceptions. After several futile attempts to synchronize access to the List by both the EDT and my background thread, I punted, and wrapped the commands to poll() the List and update the GUI in a small Runnable(), and used invokeAndWait() to cause them to run on the EDT while my background task waited.

这是我修改后的解决方案:

Here's my revised solution:

private ExecutorService executorService;
 :
executorService = Executors.newSingleThreadExecutor();
 :
/*
 * When EDT receives a request for a card it calls readCard(),
 * which queues the work to the *single* thread.
 */
public void readCard() throws Exception {
    executorService.execute(new Runnable() {
        public void run() {
            if (buffer.isEmpty()) {
                /*
                 * fill() takes 1/4 second (simulated by Thread.sleep)
                 */
                buffer.fill();
            }
            Card card = buffer.get(); // empties buffer
            /*
             * Send card to CPU
             */
            CPU.sendMessage(card); // <== (A) put card in msg queue
            /* 
             * No race!  Next request will run on same thread, after us.
             */
            buffer.fill(); //         <== (B) pre-fetch next card
            return;
        }
    });
}

/*
 * IMPORTANT MODIFICATION HERE - - -
 *
 * buffer fill() method has to remove item from the list that is the
 * model behind a JList - only safe way is to do that on EDT!
 */
private void fill() {
    SwingUtilities.invokeAndWait(new Runnable() {
        /*
         * Running here on the EDT
         */
        public void run() {
            /*
             * Hopper not empty, so we will be able to read a card.
             */
            buffer = readHopper.pollLast();  // read next card from current deck
            fireIntervalRemoved(this, readHopper.size(), readHopper.size()); 
            gui.viewBottomOfHopper(); // scroll read hopper view correctly
        }
    });
    // back to my worker thread, to do 1/4 sec. of heavy number crunching ;)
    // while leaving the GUI responsive 
    Thread.sleep(250);
     :
    etc.
}

这篇关于如何使用 SwingWorker 模拟缓冲外围设备?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆