Java Swing y concurrencia - Las solicitudes de dormir antes de una acción se lleva a cabo

gthanop:

Estoy tratando de desarrollar un medio de programación de una Runnablevez transcurrido un tiempo más corto. El código debe comenzar con una petición está realizando y contar hacia atrás hasta que haya transcurrido una cantidad de tiempo, y luego ejecutar el Runnable. Pero también necesito que no puede haber más de uno, las solicitudes presentadas, y para cada nueva solicitud será renovado la demora antes de que el Runnablese ejecuta.

El objetivo es alcanzar el siguiente comportamiento: cuando el usuario se desplaza una JList, un oyente ajuste en la barra de desplazamiento vertical de los JList's JScrollPaneva a solicitar la demora antes de que el Runnablese ejecuta. Cada vez que el usuario se desplaza hacia una nueva petición se hace, por lo que se renueva el retraso. Los solicitud devuelve inmediatamente para que la EDT es bloqueado por la menor cantidad de tiempo. Por lo que la espera y la ejecución de la Runnabledebe ocurrir de una manera diferente Thread(que la EDT). Después de que haya transcurrido un tiempo más corto, desde la última solicitud presentada, el Runnablese ejecuta.

Necesito este comportamiento porque la JListcontendrán muchos miles de miniaturas de las imágenes. No quiero cargar previamente todas las miniaturas en el JList, ya que podría no caber en la memoria. No quiero cargar imágenes en miniatura como el usuario se desplaza, ya que le puede hacer desplaza rápidamente arbitrarias Permítanme decirlo. Así que sólo quiero empezar miniaturas de carga una vez el usuario espera / instala en un solo lugar en el JListde una cantidad de tiempo (digamos por ejemplo 500 ms, 1 segundo, o algo entre).

Lo que he intentado es crear una completamente hechos a mano planificador con el trabajador Threads. Sigue mi esfuerzo, con explicaciones relativas en los comentarios:

import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.LongConsumer;

public class SleepThenActScheduler {

    public class WorkerThread extends Thread {

        //How long will we be waiting:
        private final TimeUnit sleepUnit;
        private final long sleepAmount;

        public WorkerThread(final TimeUnit sleepUnit,
                            final long sleepAmount) {
            this.sleepUnit = sleepUnit;
            this.sleepAmount = sleepAmount;
        }

        public TimeUnit getSleepUnit() {
            return sleepUnit;
        }

        public long getSleepAmount() {
            return sleepAmount;
        }

        @Override
        public void run() {
            try {
                if (sleepUnit != null)
                    sleepUnit.sleep(sleepAmount); //Wait for the specified time.
                synchronized (SleepThenActScheduler.this) {
                    if (t == this && whenDone != null) { //If we are the last request:
                        //Execute the "Runnable" in this worker thread:
                        whenDone.accept(System.currentTimeMillis() - start);
                        //Mark the operation as completed:
                        whenDone = null;
                        t = null;
                    }
                }
            }
            catch (final InterruptedException ix) {
                //If interrupted while sleeping, simply do nothing and terminate.
            }
        }
    }

    private LongConsumer whenDone; //This is the "Runnable" to execute after the time has elapsed.
    private WorkerThread t; //This is the last active thread.
    private long start; //This is the start time of the first request made.

    public SleepThenActScheduler() {
        whenDone = null;
        t = null;
        start = 0; //This value does not matter.
    }

    public synchronized void request(final TimeUnit sleepUnit,
                                     final long sleepAmount,
                                     final LongConsumer whenDone) {
        this.whenDone = Objects.requireNonNull(whenDone); //First perform the validity checks and then continue...
        if (t == null) //If this is a first request after the runnable executed, then:
            start = System.currentTimeMillis(); //Log the starting time.
        else //Otherwise we know a worker thread is already running, so:
            t.interrupt(); //stop it.
        t = new WorkerThread(sleepUnit, sleepAmount);
        t.start(); //Start the new worker thread.
    }
}

Y el uso de parecerá que el siguiente código (que me gustaría seguir siendo relevante en sus posibles respuestas si es posible):

SleepThenActScheduler sta = new SleepThenActScheduler();
final JScrollPane listScroll = new JScrollPane(jlist);
listScroll.getVerticalScrollBar().addAdjustmentListener(adjustmentEvent -> {
    sta.request(TimeUnit.SECONDS, 1, actualElapsedTime -> {
        //Code for loading some thumbnails...
    });
});

Sin embargo, este código crea una nueva Threadpara cada solicitud (e interrumpe el último). No sé si esto es una buena práctica, así que también he intentado usar un solo Threadque las espirales de dormir hasta que haya transcurrido el tiempo solicitado desde la última solicitud hecha:

import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.LongConsumer;

public class SleepThenActThread extends Thread {

    public static class TimeAmount implements Comparable<TimeAmount> {
        private final TimeUnit unit;
        private final long amount;

        public TimeAmount(final TimeUnit unit,
                          final long amount) {
            this.unit = unit;
            this.amount = amount;
        }

        public void sleep() throws InterruptedException {
            /*Warning: does not take into account overflows...
            For example what if we want to sleep for Long.MAX_VALUE days?...
            Look at the implementation of TimeUnit.sleep(...) to see why I am saying this.*/
            if (unit != null)
                unit.sleep(amount);
        }

        public TimeAmount add(final TimeAmount tammt) {
            /*Warning: does not take into account overflows...
            For example what if we want to add Long.MAX_VALUE-1 days with something else?...*/
            return new TimeAmount(TimeUnit.NANOSECONDS, unit.toNanos(amount) + tammt.unit.toNanos(tammt.amount));
        }

        @Override
        public int compareTo(final TimeAmount tammt) {
            /*Warning: does not take into account overflows...
            For example what if we want to compare Long.MAX_VALUE days with something else?...*/
            return Long.compare(unit.toNanos(amount), tammt.unit.toNanos(tammt.amount));
        }
    }

    private static TimeAmount requirePositive(final TimeAmount t) {
        if (t.amount <= 0) //+NullPointerException.
            throw new IllegalArgumentException("Insufficient time amount.");
        return t;
    }

    private LongConsumer runnable;
    private TimeAmount resolution, total;

    public SleepThenActThread(final TimeAmount total,
                              final TimeAmount resolution) {
        this.resolution = requirePositive(resolution);
        this.total = requirePositive(total);
    }

    public synchronized void setResolution(final TimeAmount resolution) {
        this.resolution = requirePositive(resolution);
    }

    public synchronized void setTotal(final TimeAmount total) {
        this.total = requirePositive(total);
    }

    public synchronized void setRunnable(final LongConsumer runnable) {
        this.runnable = Objects.requireNonNull(runnable);
    }

    public synchronized TimeAmount getResolution() {
        return resolution;
    }

    public synchronized TimeAmount getTotal() {
        return total;
    }

    public synchronized LongConsumer getRunnable() {
        return runnable;
    }

    public synchronized void request(final TimeAmount requestedMin,
                                     final LongConsumer runnable) {
        /*In order to achieve requestedMin time to elapse from this last made
        request, we can simply add the requestedMin time to the total time:*/
        setTotal(getTotal().add(requestedMin));
        setRunnable(runnable);
        if (getState().equals(Thread.State.NEW))
            start();
    }

    @Override
    public void run() {
        try {
            final long startMillis = System.currentTimeMillis();
            TimeAmount current = new TimeAmount(TimeUnit.NANOSECONDS, 0);
            while (current.compareTo(getTotal()) < 0) {
                final TimeAmount res = getResolution();
                res.sleep();
                current = current.add(res);
            }
            getRunnable().accept(System.currentTimeMillis() - startMillis);
        }
        catch (final InterruptedException ix) {
        }
    }
}

(Nota: el segundo enfoque no está totalmente depurado, pero creo que se entiende la idea.)

Y el uso de parecerá que el siguiente código:

SleepThenActThread sta = new SleepThenActThread(new TimeAmount(TimeUnit.SECONDS, 1), new TimeAmount(TimeUnit.MILLISECONDS, 10));
final JScrollPane listScroll = new JScrollPane(jlist);
listScroll.getVerticalScrollBar().addAdjustmentListener(adjustmentEvent -> {
    sta.request(new TimeAmount(TimeUnit.SECONDS, 1), actualElapsedTime -> {
        //Code for loading some thumbnails...
    });
});

Pero no sé si esto es una práctica bien tampoco, y esto también requiere mucho más tiempo de CPU supongo.

Mi pregunta, sin embargo, no es la solución más ecológica, pero es si existe una manera mejor / más formal de conseguir esto con menos conmoción / código. Por ejemplo debería utilizar un java.util.Timer, una javax.swing.Timer, o una ScheduledExecutorService? ¿Pero cómo? Supongo que algo en el java.util.concurrentpaquete debe ser una respuesta.

Realmente no importa súper precisión en el retraso como se puede imaginar.

Todas las recomendaciones en los comentarios sobre otros enfoques para lograr el mismo objetivo también sería bueno.

No estoy realmente pidiendo depuración, pero tampoco creo que esta pregunta se debe mover a la revisión de código , ya que estoy pidiendo un / una mejor solución alternativa.

Yo preferiría que esto sea en Java 8 (y por encima, si no es posible con 8).

Gracias.

Fredk:

He aquí un ejemplo del uso de un temporizador de oscilación. Al pulsar el botón reiniciará el retraso de 2 segundos.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Delay extends JPanel {
   Timer timer;
   int   presses = 0;

   public Delay() {
      setLayout(new BorderLayout());
      JButton b = new JButton("Sleep 2 seconds");
      JLabel label = new JLabel("The app is currently asleep.");
      add(b, BorderLayout.CENTER);
      add(label, BorderLayout.SOUTH);

      b.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent arg0) {
            timer.restart();
            presses++;
         }
      });

      timer = new Timer(2000, new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
            label.setText("Time expired after " + presses + " presses");

         }
      });
      timer.start();
   }

   public static void main(final String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         @Override
         public void run() {
            final JFrame jf = new JFrame();

            JPanel panel = new Delay();
            jf.add(panel);
            jf.pack();
            jf.setVisible(true);
            jf.addWindowListener(new WindowAdapter() {
               @Override
               public void windowClosing(final WindowEvent arg0) {
                  System.exit(0);
               }
            });
         }
      });
   }
}

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=276537&siteId=1
Recomendado
Clasificación