如何使用 SwingWorker 模拟缓冲外围设备?
我使用这个练习作为教学工具来帮助我记住一些 Java GUI 编程概念。我正在寻找的是一种总体理解,而不是针对一个特定问题的详细解决方案。我希望这种“正确”的编码能够教会我很多关于如何处理未来的多线程问题的知识。如果这对于这个论坛来说太笼统了,可能它属于程序员?
我正在模拟读卡器。它有一个 GUI,允许我们将卡片加载到料斗中并按“开始”等等,但它的主要“客户端”是 CPU,在单独的线程上运行并请求卡片。
读卡器维护一个缓冲区。如果收到卡片请求并且缓冲区为空,则读卡器必须从卡片盒中读取卡片(需要 1/4 秒,这是 1962 年)。将卡读入缓冲区后,读卡器将缓冲区发送到 CPU,并在下一个请求之前立即启动另一个缓冲区加载操作。
如果不仅缓冲区是空的,而且料斗中也没有卡片,那么我们必须等到操作员将一副牌放入料斗中并按下“开始”(这总是启动缓冲区加载操作)。
在我的实现中,卡请求以在 EDT 上排队的 invokeLater()
Runnables
的形式发送到读卡器。在 myRunnable.run() 时,要么缓冲区可用(在这种情况下,我们可以将其发送到 CPU 并启动另一个缓冲区加载操作),要么缓冲区为空。如果是空的怎么办?
有两种可能性:(a) 飞行中已经有缓冲加载操作,或者 (b) 卡片槽为空(或尚未启动)。无论哪种情况,让 EDT 等待都是不可接受的。工作(和等待)必须在后台线程上完成。
为了简单起见,我尝试生成一个 SwingWorker 来响应每个卡请求,无论缓冲区的状态如何。伪代码是:
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) 处的预取调用解决了这个问题。]
所以我认为为每次读取生成一个 SwingWorker 线程是错误的。卡的缓冲和发送必须在单个线程中串行化。该线程必须尝试预取缓冲区,并且如果我们用完卡片并且必须等待将更多卡片放入料斗中,则必须能够等待并恢复。我怀疑 SwingWorker 拥有长时间运行的后台线程来处理这个问题,但我还没有完全做到这一点。
假设采用 SwingWorker 线程,我该如何实现这一点,消除 EDT 上的延迟,允许线程阻塞等待料斗重新填充,并处理缓冲区填充是否在另一个卡请求到达之前或之后完成的不确定性?
编辑:我从另一个线程得到了答案,并将在这里回顾一下:
建议我不要使用 SwingWorker 线程,而是使用 SwingWorker 线程。在开始时创建一个 ExecutorService
newSingleThreadExecutor()
一次,并使用 GUI 将冗长的方法加入队列execute(Runnable foo)
,如下(此代码在 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 之间的主要区别在于,这确保只有一个工作线程。
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?
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.
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).
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?
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.
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();
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.]
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.
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?
EDIT: I got an answer from another thread and will recap it here:
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;
}
});
}
The main difference between this and SwingWorker is that this ensures there's only one worker thread.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
了解
SwingWorker
使用
ExecutorService
内部;为了方便起见,它增加了临时EDT处理机制。只要您在 EDT 上更新 GUI 并同步对任何共享数据的访问,后者就等同于前者。假设您使用 模型–视图–控制器 模式,建议 这里,你的模型就是操作一个CPU的。尽管它可能是不同的类,但我看不出有任何理由在不同的线程上对读卡器进行建模。相反,让处理器模型有一个读卡器模型来等待
java.util.Timer
线程,在计时器触发时更新模型。让更新的模型在将事件发布到 EDT 的正常过程中通知视图。让控制器取消并调度读卡器模型以响应查看手势。It may help to know that
SwingWorker
uses anExecutorService
internally; it adds the interim EDT processing mechanism for convenience. As long as you update your GUI on the EDT and synchronize access to any shared data, the latter is equivalent to the former.Assuming you are using the Model–View–Controller pattern, suggested here, your model is the operation of a cpu. Although it may be a different class, I can't see any reason to model the card reader on a different thread. Instead, let the processor model have a card reader model that does the waiting on a
java.util.Timer
thread, updating the model as the timer fires. Let the updated model notify the view in the normal course of posting events to the EDT. Let the controller cancel and schedule the card reader model in response to view gestures.我附加到原始问题的“答案”中缺少一件事:
我将耗时的工作(只不过是用于教学目的的 Thread.sleep() )交给了一个后台线程,通过单线程执行器。然而,出现了一个问题,因为后台线程通过 poll() 对充当 Swing 组件数据模型的 List 进行“读卡”,并引发大量 AWT 数组索引超出范围异常。在多次尝试同步 EDT 和我的后台线程对列表的访问但徒劳之后,我将命令包装到 poll() 列表并在一个小的 Runnable() 中更新 GUI,并使用 invokeAndWait() 来导致当我的后台任务等待时,它们会在 EDT 上运行。
这是我修改后的解决方案:
There was one thing missing from the "answer" I had appended to the original question:
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:
1) 创建 GUI,应该为空,或者基于 Java 包中的默认值
2) start
periodic = new AccurateScheduledRunnable() {...};
3) 声明
的监视器预定的未来周期监视器;
然后你会得到例如...剩余时间
4)
SwingWorker
可以通过使用支持多线程Executor executor = Executors.newCachedThreadPool();
,那么你就可以这个5)无论你期望什么......
编辑
嗯嗯AccurateScheduledRunnable 是自定义抽象类
,但为了我的享受,我构建了这个类,..给出我谈论的答案
1) create GUI, should be empty, or based on default values from Java package(s)
2) start
periodic = new AccurateScheduledRunnable() {...};
3) declare monitor for
ScheduledFuture<?> periodicMonitor;
then you get for example ... remaining time from
4)
SwingWorker
could support multithreading by usingExecutor executor = Executors.newCachedThreadPool();
, then you can be able this one5) whatever you expected ...
EDIT
hmmmm AccurateScheduledRunnable is custom Abstract Class
but for my enjoy I built this one, .. give an answer what I talked about