Domanda

Sto usando un ThreadPoolExecutor con una dimensione del pool di thread di uno per eseguire in sequenza i lavoratori swing. Ho avuto un caso speciale in cui arriva un evento che crea un lavoratore swing che esegue alcune comunicazioni client-server e successivamente aggiorna l'interfaccia utente (nel metodo done ()).

Funziona bene quando l'utente attiva (fa clic su un elemento) alcuni eventi, ma non se si verificano molti di essi. Ma questo succede, quindi devo annullare tutti i lavoratori attualmente in esecuzione e programmati. Il problema è che la coda che sta sostenendo ThreadPoolExecutor non è a conoscenza del processo di cancellazione di SwingWorker (almeno sembra così). Quindi i lavoratori programmati vengono cancellati, ma i lavoratori già in esecuzione non lo fanno.

Quindi ho aggiunto una coda simultanea di tipo <T extends SwingWorker> che contiene un riferimento di tutti i lavoratori purché non vengano annullati e quando arriva un nuovo evento chiama .cancel (true) su tutti gli SwingWorker nella coda e invia il nuovo SwingWorker per ThreadPoolExecutor.

Riepilogo: SwingWorkers viene creato ed eseguito in un ThreadPoolExecutor con un singolo thread. Solo il lavoratore che è stato inviato per ultimo dovrebbe essere in esecuzione.

Esistono alternative per risolvere questo problema, oppure è " ok " farlo in questo modo?

Solo curioso ...

È stato utile?

Soluzione

Un modo per creare un ThreadPoolExecutor a thread singolo che esegue solo l'ultimo Runnable in entrata è quello di sottoclassare una classe di coda adatta e sovrascrivere tutti i metodi di aggiunta per cancellare la coda prima di aggiungere il nuovo runnable. Quindi imposta quella coda come coda di lavoro di ThreadPoolExecutor.

Altri suggerimenti

Perché hai bisogno di un ThreadPoolExecutor per fare questo tipo di lavoro?

Quante fonti hai dei diversi SwingWorker? Perché se la fonte è solo una, dovresti usare un approccio diverso.

Ad esempio, puoi definire una classe che gestisce un tipo di thread di lavoro ed è collegata a un singolo tipo di elemento su cui l'utente può eseguire azioni e preoccuparsi all'interno di quella classe del fatto che dovrebbe essere in esecuzione una singola istanza del thread (ad esempio utilizzando un'istanza singleton che viene cancellata al termine dell'attività)

Invece di utilizzare SwingWorker non è possibile utilizzare un ThreadPoolExecutor per eseguire la comunicazione client-server e quindi chiamare SwingUtilities.invokeLater per aggiornare l'interfaccia utente con il risultato? Questo per me sembra un po 'più pulito e garantirebbe che gli eventi e gli aggiornamenti dell'interfaccia utente vengano ancora elaborati in ordine.

Quando invii le attività al tuo esecutore puoi conservare un riferimento alle loro istanze future in modo da poter annullare l'attività, se necessario.

Fammi vedere se ho capito correttamente il problema. Hai una coda di attività FIFO, solo la più vecchia delle quali è in esecuzione. Ogni attività deve aggiornare l'interfaccia utente al termine. Ma se arriva un determinato evento utente, tutte le attività devono essere annullate, ovvero le attività in esecuzione devono essere annullate e le attività non ancora in esecuzione devono essere rimosse dalla coda. Esatto?

Supponendo che lo sia, non userei SwingWorker poiché hai bisogno di un solo thread di lavoro, non di uno per attività. FutureTask dovrebbe essere sufficiente (presupponendo che si ignori done() per effettuare la chiamata necessaria a SwingUtilities.invokeLater() e eseguire l'aggiornamento dell'interfaccia utente).

Se annulli un run(), anche se viene chiamato il suo metodo ExecutorService, non farà nulla. Quindi puoi inviare FutureTasks in modo sicuro a <=> sapendo che la cancellazione funzionerà anche se l'esecutore tenta di eseguirle.

Sospetto che una soluzione sufficiente sarebbe quella di mantenere un elenco di tutti <=> che potrebbero dover essere annullati e cancellarli tutti quando si verifica l'evento utente. <=> proverà comunque a funzionare ma in pratica sarà una no-op. Devi assicurarti che le attività completate vengano rimosse dall'elenco e devi assicurarti che l'elenco sia aggiornato e utilizzato in modo sicuro (probabilmente dallo stesso thread che inserisce le attività in <=>), ma ciò non dovrebbe essere troppo difficile.

Ho rovesciato il codice qui sotto in appena un'ora e non avrei scommesso che fosse corretto, ma hai capito. :)

/** Untested code! Use at own risk. */
public class SwingTaskExecutor {

    // ////////////////////////////////////////////////////////////
    // Fields

    private final ExecutorService execSvc = Executors.newFixedThreadPool(1);

    private final Lock listLock = new ReentrantLock();
    private final List<ManagedSwingTask<?>> activeTasks = 
            new ArrayList<ManagedSwingTask<?>>();

    // ////////////////////////////////////////////////////////////
    // Public methods

    public <T> Future<T> submit(SwingTask<T> task) {
        ManagedSwingTask<T> managedTask = new ManagedSwingTask<T>(task);
        addToActiveTasks(managedTask);
        execSvc.submit(managedTask);
        return managedTask;
    }

    public void cancelAllTasks() {
        listLock.lock();
        try {
            for (ManagedSwingTask<?> t: activeTasks) {
                t.cancel(true);
            }
            activeTasks.clear();
        } finally {
            listLock.unlock();
        }
    }

    // ////////////////////////////////////////////////////////////
    // Private methods

    private <T> void addToActiveTasks(ManagedSwingTask<T> managedTask) {
        listLock.lock();
        try {
            activeTasks.add(managedTask);
        } finally {
            listLock.unlock();
        }
    }

    // ////////////////////////////////////////////////////////////
    // Helper classes

    private class ManagedSwingTask<T> extends FutureTask<T> {

        private final SwingTask<T> task;

        ManagedSwingTask(SwingTask<T> task) {
            super(task);
            this.task = task;
        }

        @Override
        public void cancel(boolean mayInterruptIfRunning) {
            try {
                task.cancel();
            } finally {
                super.cancel(mayInterruptIfRunning);
            }
        }

        @Override
        protected void done() {
            removeFromActiveTasks();
            updateUIIfDone();
        }

        private void removeFromActiveTasks() {
            listLock.lock();
            try {
                activeTasks.remove(this);
            } finally {
                listLock.unlock();
            }
        }

        private void updateUIIfDone() {
            if (isDone()) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        task.updateUI();
                    }
                });
            }
        }
    }

    public static interface SwingTask<T> extends Callable<T> {

        /** Called from the EDT if task completes successfully */
        void updateUI();

        /** Hook in case there's task-specific cancellation to be done*/
        void cancel();
    }
}

Qualcosa del genere, comunque.

Se vuoi essere doppiamente sicuro, puoi chiudere e sostituire <=>, ma probabilmente non è necessario.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top