¿Cómo simulo un dispositivo periférico con buffer con SwingWorker?

Estoy usando este ejercicio como una herramienta pedagógica para ayudarme a quemar algunos conceptos de progtwigción de la GUI de Java. Lo que estoy buscando es una comprensión general, en lugar de una solución detallada a un problema específico. Espero que la encoding de este “derecho” me enseñe mucho sobre cómo abordar futuros problemas de subprocesos múltiples. Si esto es demasiado general para este foro, ¿posiblemente pertenece a los progtwigdores?

Estoy simulando un lector de tarjetas. Tiene una GUI, lo que nos permite cargar tarjetas en la tolva y presionar Start, etc., pero su principal “cliente” es la CPU, ejecutándose en un hilo separado y solicitando tarjetas.

El lector de tarjetas mantiene un solo buffer. Si entra una solicitud de tarjeta y el búfer está vacío, el lector de tarjetas debe leer una tarjeta de la tolva (que toma 1/4 de segundo, siendo 1962). Después de haber leído la tarjeta en el búfer, el lector de tarjetas envía el búfer a la CPU, e inmediatamente inicia otra operación de carga del búfer, antes de la próxima solicitud.

Si no solo el buffer está vacío, pero no hay tarjetas en la tolva, entonces debemos esperar hasta que el operador haya colocado una plataforma en la tolva y presione Start (que siempre inicia una operación de carga de buffer).

En mi implementación, las solicitudes de tarjeta se envían al lector de tarjetas en forma de invokeLater() Runnables cola en el EDT. En myRunnable.run() , estará disponible un búfer (en cuyo caso podemos enviarlo a la CPU y poner en marcha otra operación de carga de búfer) o el búfer estará vacío. ¿Qué pasa si está vacío?

Dos posibilidades: (a) ya hay una operación de carga de memoria intermedia en vuelo, o (b) la tolva de tarjeta está vacía (o no se ha iniciado). En cualquier caso, no es aceptable mantener el EDT en espera. El trabajo (y la espera) se debe hacer en un hilo de fondo.

En aras de la simplicidad, intenté generar un SwingWorker en respuesta a cada solicitud de tarjeta, independientemente del estado del buffer. El pseudocódigo fue:

 SwingWorker worker = new SwingWorker() { 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(); 

Esto produjo algunos efectos de temporización impares, debido, sospecho, a una carrera buffer.fill() que podría ocurrir de la siguiente manera: si, entre (A) y (B), la CPU recibió la tarjeta, envió una solicitud para otra, y si otro hilo de SwingWorker generó en su nombre, entonces podría haber dos hilos tratando simultáneamente de llenar el búfer. [Eliminar la llamada de recuperación previa en (B) solucionó eso.]

Así que creo que engendrar un hilo de SwingWorker para cada lectura es incorrecto. El almacenamiento en búfer y envío de tarjetas debe ser serializado en un solo hilo. Ese hilo debe intentar prearchivar un búfer, y debe poder esperar y reanudar si nos quedamos sin tarjetas y tenemos que esperar que se coloque más en la tolva. Sospecho que SwingWorker tiene lo que se requiere para ser un hilo de fondo de larga duración para manejar esto, pero todavía no estoy allí.

Asumiendo que un hilo SwingWorker es el camino a seguir, ¿cómo podría implementar esto, eliminando el retraso en el EDT, permitiendo que el hilo se bloquee esperando una recarga de tolva, y manejando la incertidumbre de si el llenado del buffer se completa antes o después de que llegue otra solicitud?


EDITAR: obtuve una respuesta de otro hilo y la resumiré aquí:

En lugar de usar un hilo SwingWorker, se recomienda crear un ExecutorService newSingleThreadExecutor() una vez, al principio, y hacer que la GUI encola los métodos largos en él usando execute(Runnable foo) , como sigue (este código se ejecuta en el 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; } }); } 

La principal diferencia entre esto y SwingWorker es que esto asegura que solo hay un hilo de trabajo.

Puede ser útil saber que SwingWorker utiliza un ExecutorService internamente; agrega el mecanismo de procesamiento EDT provisional por conveniencia. Siempre y cuando actualice su GUI en el EDT y sincronice el acceso a cualquier información compartida, esta última es equivalente a la anterior.

Suponiendo que está utilizando el patrón Modelo-Vista-Controlador , que se sugiere aquí , su modelo es la operación de una CPU. Aunque puede ser una clase diferente, no veo ninguna razón para modelar el lector de tarjetas en un hilo diferente. En su lugar, permita que el modelo de procesador tenga un modelo de lector de tarjetas que haga la espera en un subproceso java.util.Timer , actualizando el modelo a medida que se dispara el temporizador. Deje que el modelo actualizado notifique la vista en el curso normal de la publicación de eventos en el EDT. Deje que el controlador cancele y programe el modelo del lector de tarjetas en respuesta a los gestos de visualización.

Faltaba una cosa en la “respuesta” que adjunté a la pregunta original:

Estaba entregando el trabajo que consumía mucho tiempo (nada más que un Thread.sleep() para fines pedagógicos) a un hilo de fondo, a través de un Single Thread Executor. Sin embargo, surgió un problema porque el hilo de fondo era “leer una tarjeta” al sondear la Lista que estaba sirviendo como modelo de datos para un componente Swing, y elevar muchas excepciones de índice AWT array out of range. Después de varios bashs inútiles de sincronizar el acceso a la Lista tanto por el EDT como por mi hilo de fondo, di un puntapié y envolví los comandos para sondear () la Lista y actualizar la GUI en un Runnable pequeño (), y utilicé invokeAndWait () para causar ellos para ejecutar en el EDT mientras esperaba mi tarea de fondo.

Aquí está mi solución revisada:

 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) crear GUI, debe estar vacío o en función de los valores predeterminados de los paquetes de Java

2) start periodic = new AccurateScheduledRunnable() {...};

  periodicMonitor = scheduler.scheduleAtFixedRate(periodic, 0, taskPeriod, TimeUnit.MINUTES); 

3) declarar monitor para ScheduledFuture periodicMonitor; entonces obtienes por ejemplo … tiempo restante de

 periodic = new AccurateScheduledRunnable() {...}; long she = periodicMonitor.getDelay(TimeUnit.SECONDS); 

4) SwingWorker podría admitir multihilo con Executor executor = Executors.newCachedThreadPool(); , entonces puedes ser este

5) lo que sea que esperabas …

EDITAR

hmmmm AccurateScheduledRunnable es clase abstracta personalizada

pero para mi gusto construí este, … dar una respuesta de lo que hablé

enter image description here

 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 { 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 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; } } 
    Intereting Posts