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

查看:227
本文介绍了如何使用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,允许我们加载卡到料斗,按开始等等,但它的主要客户端是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() 的形式发送到读卡器。 / code>正在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();

这产生了一些奇怪的时序效果 - 我怀疑,到一个缓冲区.fill() race可能发生如下:如果,在(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:

而不是使用SwingWorker线程,建议我创建一个 ExecutorService newSingleThreadExecutor()一次,在开头,并且使用 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和我的后台线程同步访问列表的几个无用的尝试后,我punted,并包装命令poll()List和更新GUI在一个小的Runnable(),并使用invokeAndWait()

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天全站免登陆