Ottieni progressi dalla classe che viene anche usata in un ambiente senza gui

1

Qual è il modo più pulito per ottenere progressi da una classe che sarà utilizzata anche in un ambiente senza gui allo scopo di visualizzare detto stato in un Gui JavaFX?

Esempio:

import java.util.ArrayList;
import java.util.List;

public class ExampleFetcher {
    public List<Entity> fetch() {
        final List<Entity> results = new ArrayList<>();

        for (int i = 0; i < N; i++) {
            final Entity current = //Do some heavy work

            results.add(current);
        }

        return results;
    }
}

Ci sono diverse possibilità, ma per me ognuna ha alcuni inconvenienti

1. "Il modo JavaFX": utilizza una proprietà osservabile da javafx.beans

import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;

import java.util.ArrayList;
import java.util.List;

public class ExampleFetcher {
    private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper();

    public List<Entity> fetch() {

        final List<Entity> results = new ArrayList<>();

        for (int i = 0; i < N; i++) {
            final Entity current = //Do some heavy work

            progress.set((double) i / N);

            results.add(current);
        }

        return results;
    }

    public double getProgress() {
        return progress.get();
    }

    public ReadOnlyDoubleProperty progressProperty() {
        return progress.getReadOnlyProperty();
    }
}

Ora puoi semplicemente osservare la progressProperty o aggiungere un listener, che è abbastanza bello, ma ingombra l'API un po '. Inoltre, è anche possibile che una classe abbia una dipendenza su javafx se non verrà utilizzata in alcuni casi?

2. Il modo ingenuo: aggiungi consumatori al metodo chiamato

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class ExampleFetcher {
    public List<Entity> fetch(final Consumer<Double> progressConsumer) {
        final List<Entity> results = new ArrayList<>();

        for (int i = 0; i < N; i++) {
            final Entity current = //Do some heavy work

            progressConsumer.accept((double) i / N);

            results.add(current);
        }

        return results;
    }
}

Puoi passare un consumatore che aggiornerà la barra di avanzamento o qualsiasi altra cosa tu stia utilizzando nella GUI. Questo evita la dipendenza da javafx ma aggiunge molta confusione all'API, specialmente se non stai monitorando solo i progressi ma qualche altro tipo di stato.

Ora, c'è un altro modo a cui non sto pensando? Uno dei modi forniti è effettivamente decente? O dovrei forse cercare un altro modo di eseguire i miei compiti?

    
posta Marv 05.02.2018 - 20:21
fonte

2 risposte

1

A seconda dei tuoi obiettivi e finalità qui, incoraggerei strongmente la soluzione n. 2 per la maggior parte degli scenari - tuttavia, c'è un'altra opzione non menzionata che presenterò solo per completezza:

Puoi presentare una proprietà standard e aspettarti che le persone la indaghino in qualunque periodo sia più adatta a loro come il consumatore. Questo non è l'ideale, e non particolarmente piacevole, ma in determinati scenari in cui si desidera avere tempi di aggiornamento garantiti per il proprio progresso (se si verificano più eventi e si desidera aggiornare i progressi tra le loro attività, ad esempio), è possibile seguire questa rotta.

import java.util.ArrayList;
import java.util.List;

public class ExampleFetcher implements Runnable {
    private progress double = 0.0;
    private List<Entity> results = new ArrayList<>();

    public void run() {
        for (int i = 0; i < N; i++) {
            final Entity current = //Do some heavy work

            progress = ((double) i / N);

            results.add(current);
        }
    }

    public double getProgress() {
        return progress;
    }

    public List<Entity> getResults() {
        return results;
    }
}

public class SomeApp {

    public void doStuff() {

        final ExampleFetcher fetcher1 = new ExampleFetcher();
        final ExampleFetcher fetcher2 = new ExampleFetcher();
        final Thread f1Thread = new Thread(fetcher1);
        final Thread f2Thread = new Thread(fetcher2);
        f1Thread.start();
        f2Thread.start();

        while(!f1Thread.isAlive || !f2Thread.isAlive) {
            Thread.sleep(500);
        }

        while(f1Thread.isAlive && f2Thread.isAlive) {
            final double f1Progress = fetcher1.getProgress();
            // do something with progress! Or.. whatever you want?
            final double f2Progress = fetcher2.getProgress();
            // do something with progress! Or.. whatever you want?
            Thread.sleep(500);
        }
    }
}

Solo il vero vantaggio è che quando firmi i callback tra le tue attività nella soluzione # 2 che hai descritto sopra, utilizzerai il thread di lavoro per fare gli aggiornamenti dato che questo thread esegue il callback (questo potrebbe significare thread UI azioni, che possono diventare problematiche). Quindi ogni callback dovrebbe fare alcune comunicazioni cross-thread per non bloccare il funzionamento dei propri fetch mentre sta facendo aggiornamenti di avanzamento, o se ha bisogno di inviare aggiornamenti a un thread dell'interfaccia utente, rendendo i callback un po 'estranei.

Francamente, questo è totalmente risolvibile scrivendo i tuoi callback con schemi semplici che li rendono non bloccanti e utilizzando la comunicazione thread standard. Vorrei ancora andare personalmente con la soluzione # 2.

La mia soluzione di polling qui descritta è occasionalmente preferibile. Come ho detto sopra, se volevi controllare quando gli aggiornamenti accadono piuttosto che lasciarli accadere ogni volta che il fetcher ha un aggiornamento, anche inserendoli in questo modo puoi avere più di una volta in esecuzione anziché se sono in esecuzione sullo stesso thread usando callback (anche con l'approccio callback, la condivisione di un thread è sconsigliata), dovranno essere eseguiti in sequenza poiché potrebbe essere necessario condividere il thread front-end. L'approccio di polling consente anche un monitoraggio integrato che ti consente di impostare i timeout per quanto tempo è consentito tra gli aggiornamenti di avanzamento. Se ti aspetti che i callback ti avvisino quando sono stati compiuti dei progressi e che il fetcher si blocca o muore prima di completare, richiederà comunque un qualche tipo di monitor per garantire di sapere "è finito! Invariato dopo 5 minuti."

Spunti di riflessione! Appoggia la soluzione n. 2 a meno che tu non abbia un motivo molto specifico per preferire il polling!

    
risposta data 06.02.2018 - 16:46
fonte
1

In caso di dubbio, utilizzare la soluzione più generale possibile. Qui, sta usando un callback, cioè il Consumer (soluzione # 2). Puoi anche pensare a questo come ProgressWatchingStrategy se è utile.

Se richiesto da un utente di questa classe, la proprietà JavaFX può quindi essere implementata banalmente, ad esempio:

ReadOnlyDoubleWrapper progressProperty = new ReadOnlyDoubleWrapper();
... // set up listeners
fetcher.fetch(progressProperty::set);

Sebbene la soluzione basata sulla proprietà (n. 1) sia effettivamente equivalente (si potrebbe anche implementare la soluzione basata su callback su di essa), introduce concetti aggiuntivi e richiede più codice per sostanzialmente nessun guadagno. L'unica ragione per preferire il campo proprietà sarebbe se l'oggetto fetcher fosse usato più volte, e gli stessi ascoltatori sarebbero interessati allo stato di più chiamate fetch() su quello stesso oggetto.

    
risposta data 05.02.2018 - 20:39
fonte

Leggi altre domande sui tag