如何使用SwingWorker模拟缓冲的外围设备?
我将这个练习作为一个教学工具来帮助我刻录一些Java GUI编程概念。 我在寻找的是一个普遍的理解,而不是一个具体问题的详细解决scheme。 我期望编写这个“正确的”将教会我很多关于如何处理未来的multithreading问题。 如果这个论坛太笼统,可能属于程序员?
我正在模拟一个读卡器。 它有一个graphics用户界面(GUI),允许我们将卡片加载到漏斗中,然后按下开始(Start)等,但其主要的“客户端”是CPU,运行在单独的线程和请求卡上。
读卡器维护一个缓冲区。 如果卡片请求进来,而且缓冲区是空的,读卡器必须从漏斗读取一张卡片(这需要1/4秒钟,这是1962年)。 卡被读入缓冲区后,读卡器将缓冲区发送给CPU,并在下一个请求之前立即启动另一个缓冲区加载操作。
如果不仅缓冲器是空的,而且料斗中没有卡片,那么我们必须等到操作员在料斗中放置了一个平台并按下开始(总是启动缓冲器加载操作)。
在我的实现中,卡片请求以invokeLater()
方式发送到读卡器,在EDT上排队运行。 在myRunnable.run()
时间,一个缓冲区将是可用的(在这种情况下,我们可以把它发送到CPU,并启动另一个缓冲区加载操作),或缓冲区将是空的。 如果它是空的呢?
两种可能性:(a)飞行中已经有一个缓冲载入操作,或者(b)卡片仓是空的(或尚未启动)。 无论哪种情况,保持美国东部时间都是不可接受的。 工作(和等待)必须在后台线程上完成。
为了简单起见,我试图产生一个SwingWorker来响应每个卡请求,而不pipe缓冲区的状态如何。 伪代码是:
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上的延迟,允许线程阻塞等待一个漏斗重新填充,并处理caching填充是否在另一个卡请求之前或之后完成的不确定性?
编辑:我从另一个线程得到一个答案,并会在这里回顾一下:
build议我一开始就创build一个ExecutorService
newSingleThreadExecutor()
,并使用execute(Runnable foo)
对GUI进行冗长newSingleThreadExecutor()
,如下所示(此代码在EDT中运行execute(Runnable foo)
,而不是使用SwingWorker线程:
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的主要区别在于,这确保了只有一个工作线程。
这可能有助于了解SwingWorker
在内部使用ExecutorService
; 它为了方便添加了临时的EDT处理机制。 只要您更新EDT上的GUI并同步访问任何共享数据,后者与前者相同。
假设你正在使用模型 – 视图 – 控制器模式, 这里build议你的模型是一个CPU的操作。 虽然它可能是一个不同的类,我看不出任何理由模型读卡器在不同的线程。 相反,让处理器模型有一个读卡器模型,在java.util.Timer
线程上等待,在计时器触发时更新模型。 让更新后的模型在正常向EDT发布事件的过程中通知视图。 让控制器取消并安排读卡器型号以响应查看手势。
从原来的问题附加的“答案”中遗漏了一件事:
为了教学目的,我将这个耗时的工作交给了后台线程Thread.sleep()
),通过一个单线程执行程序。 然而,出现了一个问题,因为后台线程是通过轮询(作为Swing组件的数据模型的List)“读取一张卡片”,并将大量的AWT数组索引超出范围例外。 经过几次无用的尝试,以同时访问EDT和我的后台线程的列表,我踢了,包装命令poll()列表和更新GUI在一个小的Runnable(),并使用invokeAndWait()他们在我的后台任务等待时在EDT上运行。
这是我的修改解决scheme:
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. }
1)创buildGUI,应该是空的,或者基于Java包中的默认值
2)启动periodic = new AccurateScheduledRunnable() {...};
periodicMonitor = scheduler.scheduleAtFixedRate(periodic, 0, taskPeriod, TimeUnit.MINUTES);
3)为ScheduledFuture<?> periodicMonitor;
声明监视器ScheduledFuture<?> periodicMonitor;
那么你得到例如…剩余的时间
periodic = new AccurateScheduledRunnable() {...}; long she = periodicMonitor.getDelay(TimeUnit.SECONDS);
4) SwingWorker
可以通过使用Executor executor = Executors.newCachedThreadPool();
来支持multithreadingExecutor executor = Executors.newCachedThreadPool();
,那么你可以做到这一点
5)无论你预期…
编辑
hmmmm AccurateScheduledRunnable是自定义抽象类
但为了我的喜好,我build立了这个,..给我一个答案,我谈到
import java.awt.*; import java.awt.event.ActionEvent; import java.beans.*; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.swing.*; import javax.swing.table.*; public class TableIcon extends JFrame implements Runnable { private static final long serialVersionUID = 1L; private JTable table; private JLabel myLabel = new JLabel("waiting"); private JLabel lastRunLabel = new JLabel("waiting"); private int pHeight = 40; private boolean runProcess = true; private int count = 0; private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); private ScheduledExecutorService scheduler; private AccurateScheduledRunnable periodic; private ScheduledFuture<?> periodicMonitor; private Executor executor = Executors.newCachedThreadPool(); private Date dateLast; private Date dateNext; private Date dateRun; private int taskPeriod = 1; private int dayCount = 0; private int hourCount = 0; private int minuteCount = 0; private int secondCount = 0; private Timer timerRun; private int delay = 3000; private boolean bolo = false; public TableIcon() { ImageIcon errorIcon = (ImageIcon) UIManager.getIcon("OptionPane.errorIcon"); ImageIcon infoIcon = (ImageIcon) UIManager.getIcon("OptionPane.informationIcon"); ImageIcon warnIcon = (ImageIcon) UIManager.getIcon("OptionPane.warningIcon"); String[] columnNames = {"Picture", "Description"}; Object[][] data = {{errorIcon, "About"}, {infoIcon, "Add"}, {warnIcon, "Copy"},}; DefaultTableModel model = new DefaultTableModel(data, columnNames) { private static final long serialVersionUID = 1L; @Override public Class getColumnClass(int column) { return getValueAt(0, column).getClass(); } }; table = new JTable(model); table.setRowHeight(pHeight); table.setPreferredScrollableViewportSize(table.getPreferredSize()); JScrollPane scrollPane = new JScrollPane(table); add(scrollPane, BorderLayout.CENTER); lastRunLabel.setPreferredSize(new Dimension(200, pHeight)); lastRunLabel.setHorizontalAlignment(SwingConstants.CENTER); add(lastRunLabel, BorderLayout.NORTH); myLabel.setPreferredSize(new Dimension(200, pHeight)); myLabel.setHorizontalAlignment(SwingConstants.CENTER); add(myLabel, BorderLayout.SOUTH); scheduler = Executors.newSingleThreadScheduledExecutor(); periodic = new AccurateScheduledRunnable() { private final int ALLOWED_TARDINESS = 200; private int countRun = 0; private int countCalled = 0; @Override public void run() { countCalled++; if (this.getExecutionTime() < ALLOWED_TARDINESS) { countRun++; executor.execute(new TableIcon.MyTask("GetCurrTime")); // non on EDT } } }; periodicMonitor = scheduler.scheduleAtFixedRate(periodic, 0, taskPeriod, TimeUnit.MINUTES); periodic.setThreadMonitor(periodicMonitor); new Thread(this).start(); prepareStartShedule(); } private void prepareStartShedule() { timerRun = new javax.swing.Timer(delay, startCycle()); timerRun.setRepeats(true); timerRun.start(); } private Action startCycle() { return new AbstractAction("Start Shedule") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { executor.execute(new TableIcon.MyTask("StartShedule")); // non on EDT } }; } private void changeTableValues() { Runnable doRun = new Runnable() { @Override public void run() { if (bolo) { bolo = false; table.getModel().setValueAt("*/*/*/**/*/*/*", 0, 1); table.getModel().setValueAt(" kkkkkkkk", 1, 1); table.getModel().setValueAt("@#@#@#@", 2, 1); } else { bolo = true; table.getModel().setValueAt("Green Peper", 0, 1); table.getModel().setValueAt("Yellow Apple", 1, 1); table.getModel().setValueAt("Orange Bus", 2, 1); } } }; SwingUtilities.invokeLater(doRun); } private void distAppInfo() { Runnable doRun = new Runnable() { @Override public void run() { dateNext = new java.util.Date(); dateLast = new java.util.Date(); long tme = dateNext.getTime(); tme += (taskPeriod * 60) * 1000; dateNext.setTime(tme); lastRunLabel.setText("Last : " + sdf.format(dateLast) + " / Next : " + sdf.format(dateNext)); } }; SwingUtilities.invokeLater(doRun); } private void changeLabelColor() { Runnable doRun = new Runnable() { @Override public void run() { Color clr = lastRunLabel.getForeground(); if (clr == Color.red) { lastRunLabel.setForeground(Color.blue); } else { lastRunLabel.setForeground(Color.red); } } }; SwingUtilities.invokeLater(doRun); } @Override public void run() { while (runProcess) { try { Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } executor.execute(new TableIcon.MyTask("ChangeIconLabel")); // non on EDT } } private void setIconLabel() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { String text = ""; dateRun = new java.util.Date(); long tme = dateRun.getTime(); long she = periodicMonitor.getDelay(TimeUnit.SECONDS); dayCount = (int) (she / (24 * 60 * 60)); hourCount = (int) (she / (60 * 60)); minuteCount = (int) (she / (60)); secondCount = (int) she; int hourss = hourCount; int minutess = minuteCount; if (dayCount > 0) { hourCount -= (dayCount * 24); minuteCount -= ((dayCount * 24 * 60) + (hourCount * 60)); secondCount -= (minutess * 60); //System.out.println(" Days : " + dayCount + " ,Hours : " + hourCount + " , Minutes : " + minuteCount + " , Seconds : " + secondCount); text = (" " + dayCount + " Days " + hourCount + " h : " + minuteCount + " m : " + secondCount + " s"); } else if (hourCount > 0) { minuteCount -= ((hourss * 60)); secondCount -= (minutess * 60); //System.out.println(" Hours : " + hourCount + " , Minutes : " + minuteCount + " , Seconds : " + secondCount); text = (" " + hourCount + " h : " + minuteCount + " m : " + secondCount + " s"); } else if (minuteCount > 0) { secondCount -= (minutess * 60); //System.out.println(" Minutes : " + minuteCount + " , Seconds : " + secondCount); text = (" " + minuteCount + " m : " + secondCount + " s"); } else { //System.out.println(" Seconds : " + secondCount); text = (" " + secondCount + " s"); } tme += she * 1000; ImageIcon myIcon = (ImageIcon) table.getModel().getValueAt(count, 0); String lbl = "Row at : " + count + " Remains : " + text; myLabel.setIcon(myIcon); myLabel.setText(lbl); count++; if (count > 2) { count = 0; } } }); } public static void main(String[] args) { TableIcon frame = new TableIcon(); frame.setDefaultCloseOperation(EXIT_ON_CLOSE); frame.setLocation(150, 150); frame.pack(); frame.setVisible(true); } private class MyTask extends SwingWorker<Void, Integer> { private String str; private String namePr; MyTask(String str) { this.str = str; addPropertyChangeListener(new SwingWorkerCompletionWaiter(str, namePr)); } @Override protected Void doInBackground() throws Exception { if (str.equals("GetCurrTime")) { distAppInfo(); } else if (str.equals("ChangeIconLabel")) { setIconLabel(); } else if (str.equals("StartShedule")) { changeTableValues(); } return null; } @Override protected void process(List<Integer> progress) { //System.out.println(str + " " + progress.get(progress.size() - 1)); } @Override protected void done() { if (str.equals("GetCurrTime")) { changeLabelColor(); } else if (str.equals("ChangeIconLabel")) { //setIconLabel(); } else if (str.equals("StartShedule")) { //changeTableValues(); } } } private class SwingWorkerCompletionWaiter implements PropertyChangeListener { private String str; private String namePr; SwingWorkerCompletionWaiter(String str, String namePr) { this.str = str; this.namePr = namePr; } SwingWorkerCompletionWaiter(String namePr) { this.namePr = namePr; } @Override public void propertyChange(PropertyChangeEvent event) { if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) { System.out.println("Thread Status with Name :" + str + ", SwingWorker Status is " + event.getNewValue()); } else if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.PENDING == event.getNewValue()) { System.out.println("Thread Status with Mame :" + str + ", SwingWorker Status is " + event.getNewValue()); } else if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.STARTED == event.getNewValue()) { System.out.println("Thread Status with Name :" + str + ", SwingWorker Status is " + event.getNewValue()); } else { System.out.println("SomeThing Wrong happends with Thread Status with Name :" + str); } } } } abstract class AccurateScheduledRunnable implements Runnable { private ScheduledFuture<?> thisThreadsMonitor; public void setThreadMonitor(ScheduledFuture<?> monitor) { this.thisThreadsMonitor = monitor; } protected long getExecutionTime() { long delay = -1 * thisThreadsMonitor.getDelay(TimeUnit.MILLISECONDS); return delay; } }