Autore Topic: Terminare un thread che usa inputstream.read() (blocking operation)  (Letto 230 volte)

Offline berpao

  • Utente junior
  • **
  • Post: 74
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S2
  • Play Store ID:
    Paolo Bersan
0
Ciao,
mi sono appena accorto di un errore nella mia app; provo a descrivervi lo scenario come meglio posso. Nella mia app ho costruito una Activity che altre activity ereditano; questo permette alle activity figlie di creare un thread secondario il cui scopo è quello di gestire l'input che arriva da un lettore di codice a barre integrato. Per leggere l'input del lettore, uso un InputStream e il relativo metodo read(); quando il bottone del lettore viene pigiato, quello che viene letto viene inviato sulla porta seriale configurata (che in soldoni da quello che ho visto, si riduce ad un file fisico sul device). A questo punto, la mia istanza di InputStream legge i dati arrivati, fa quello che deve fare e si rimette in ascolto. Quando chiudo la mia activity, nell'evento "onPause" della classe madre, stoppo il thread creato precedentemente e qui c'è il problema; se io in questa fase schiacciassi di nuovo il bottone del lettore, anzichè non fare niente come mi sarei aspettato perchè il thread che gestisce l'inpustream l'ho "terminato", il metodo inputstream.read() del thread parte lo stesso ancora una volta. Facendo le varie analisi del codice questo sembra essere un comportamento corretto perchè, essendo il metodo read() un metodo bloccante, rimane li in attesa fino  a quando non ne forzo l'esecuzione almeno un'altra volta; a questo punto, verificando lo stato del thread, il ciclo while infinito si blocca e il thread muore correttemante. La mia domanda è: c'è modo di risolvere questa situazione, in modo tale che quando chiamo il thread.terminate() in qualche maniera forzo lo stop del ciclo infinito. Ho provato a distruggere le istanze dell'inputstream ma non è servito a niente. Ho provato ad inviare all'inputstream del testo in modo da "forzare" il metodo "read" a leggerne il contenuto e così da far ripartire il ciclo e poter verificare lo stato del thread, ma niente. Vi allego un po' di codice:

Codice (Java): [Seleziona]
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidParameterException;
import android.os.Bundle;
import android.util.Log;
import com.flsmidthventomatic.android.support.CommonFunctions;
import com.flsmidthventomatic.wmsapp.R;

import android_serialport_api.SerialPort;

public abstract class SerialPortActivity extends WmsActivity {

        protected ApplicationExt mApplication;
        protected SerialPort mSerialPort;
        protected OutputStream mOutputStream;
        protected InputStream mInputStream;
        protected ReadThread mReadThread;
        private final String TAG="SerialPortActivity";

        /***
         * Definizione thread
         */

        public class ReadThread extends Thread {
                OutputStream outStream;
                InputStream inStream;
                byte[] buffer;

                /**
                 *
                 * @param outStream  OutputStream sulla porta seriale /dev/ttyHSL1
                 * @param inStream   InputStream sulla porta seriale /dev/ttyHSL1
                 * @param bufferSize Dimensione del budder di lettura sulla inputstream
                 */

                public ReadThread(OutputStream outStream,InputStream inStream,int bufferSize) {
                        this.outStream=outStream;
                        this.inStream=inStream;
                        this.buffer = new byte[bufferSize];
                }

                /**
                 * Metodo per "pulire" gli oggetti inputstream/outputstream
                 */

                public void purgeInternalObjects() {
                        try {
                                this.outStream.flush();
                                this.outStream.close();
                                this.outStream=null;
                                this.inStream.close();
                                this.inStream=null;
                        } catch(Exception ex) {

                        }
                }

                /**
                 * Tentativo di forzare la inputstream a leggere dei dati per poter terminare il thread
                 * Ho ipotizzato che scrivendo dei dati nell'outputstream della porta seriale, il metodo inputstream.read() li leggesse e il ciclo non fosse più bloccato.
                 * NON HA FUNZIONATO
                 */

                public void sendBlockFlag(){
                        try {

                                byte[] endByteSymbol = new byte[64];//"END".getBytes();
                                this.outStream.write(endByteSymbol);
                                this.outStream.flush();
                                this.outStream.close();

                        } catch(Exception ex) {

                        }
                }

                /***
                 * Run del thread con ciclo while infinito
                 */

                @Override
                public void run() {
                        try {
                                super.run();

                                //Ciclo while basato sullo stato del thread
                                while(!isInterrupted()) {
                                        int size;
                                        try {
                                                if (this.inStream == null) return;

                                                //BLOCKING METHOD: qui il ciclo si ferma finquando non arrivano dati nella porta seriale
                                                //Questo implica che quando termino il thread, la inputstream rimane attiva e finquando non faccio arrivare nuovi dati è ferma qui
                                                //Premendo il tasto del lettore, la inputstream rilegge i nuovi dati arrivati, passa all'istruzione successiva...
                                                size = this.inStream.read(this.buffer);

                                                //...e se il thread è interrotto non fa nulla. A questo punto il ciclo while si interrompe
                                                if (size > 1 && !isInterrupted()) {
                                                        //questo è il mio handler, ciò che viene fatto quando schiaccio il bottone del barcode.
                                                        //Ogni activity figlia implementa la propria logica
                                                        onDataReceived(this.buffer, size, true);
                                                }
                                        } catch (IOException e) {
                                                Log.d(TAG,e.getMessage());
                                                e.printStackTrace();
                                                return;
                                        }
                                }
                        } catch (Exception e) {
                                Log.d(TAG,e.getMessage());
                                e.printStackTrace();
                                return;
                        }
                }
        }

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
        }

        //Definizione del mio evento
        protected abstract void onDataReceived(final byte[] buffer, final int size,boolean fromScanner);

        /***
         * Metodo chiamato nell'onResume. Crea il thread
         */

        protected void startBackgroundReading() {
                try {
                        mApplication = (ApplicationExt) getApplication();
                        mSerialPort = mApplication.getSerialPort();
                        mOutputStream = mSerialPort.getOutputStream();
                        mInputStream = mSerialPort.getInputStream();

                        /* Create a receiving thread*/
                        //mReadThread = new ReadThread();
                        mReadThread = new ReadThread(mOutputStream,mInputStream,64);
                        mReadThread.start();
                } catch (SecurityException e) {
                        //DisplayError(R.string.error_security);
                        CommonFunctions.showErrorsToUser(this,e);
                } catch (IOException e) {
                        //DisplayError(R.string.error_unknown);
                        CommonFunctions.showErrorsToUser(this,e);
                } catch (InvalidParameterException e) {
                        //DisplayError(R.string.error_configuration);
                        CommonFunctions.showErrorsToUser(this,e);
                }
        }

        /***
         * Metodo chiamato nell'onPause. Termina il thread
         * QUI DOVREI RIUSCIRE A STOPPARE ANCHE LA READ() DELLA INPUTSTREAM
         */

        protected void stopBackgroundReading() {
                try {
                        if (mReadThread != null) {
                                mReadThread.interrupt();
                                //Send data to output stream to force read it, in order to re-read the thread flag and terminate it
                                //mReadThread.sendBlockFlag();
                                mReadThread.purgeInternalObjects();
                                mReadThread = null;
                        }

                        mApplication.closeSerialPort();
                        mOutputStream = null;
                        mInputStream = null;
                        mSerialPort = null;
                } catch(Exception ex) {
                        Log.d("thread",ex.getMessage());
                }
        }

        @Override
        protected void onResume() {
                super.onResume();
                startBackgroundReading();
        }

        @Override
        protected void onPause() {
                this.stopBackgroundReading();
                super.onPause();
        }

        @Override
        protected void onDestroy() {
                super.onDestroy();
        }
}

Nelle classi figlie l'unica cosa da fare è implementare il metodo per gestire i dati ricevuti via barcode.

Seconda domanda (più una curiosità che altro); il metodo inputstream.read() quando stabilisce che è il momento di leggere i dati o cmq si accorge che sono arrivati nuovi dati? Nella documentazione java c'è scritto:

Citazione
Reads some number of bytes from the input stream and stores them into the buffer array b. The number of bytes actually read is returned as an integer. This method blocks until input data is available, end of file is detected, or an exception is thrown.

La parte che ho evidenziato in grassetto è quella che non sto capendo, se qualcuno me la può spiegare... pensavo che inviare dei dati tramite una istanza di una outputstream fosse sufficiente ma evidentemente  no (oppure ho sbagliato io ad usarla)
Ciao e grazie

Offline Ohmnibus

  • Utente senior
  • ****
  • Post: 763
  • Respect: +159
    • Github
    • Google+
    • @ohmnibus
    • Mostra profilo
    • Lords of Knowledge GdR
  • Dispositivo Android:
    Huawei P9 Lite
  • Play Store ID:
    Ohmnibus
  • Sistema operativo:
    Windows 7 x64
Re:Terminare un thread che usa inputstream.read() (blocking operation)
« Risposta #1 il: 03 Ottobre 2017, 11:32:41 CEST »
0
Per quanto riguarda il secondo punto il comportamento è quello previsto per gli stream.

Uno stream rappresenta un flusso di dati. Questo flusso può essere più o meno continuo in base alla situazione, e di lunghezza solitamente non nota. Per cui chi legge lo stream si mette in ascolto di dati in arrivo e, man mano che arrivano, li legge riempiendo un buffer. Finché il buffer non è pieno (o arriva un End Of Stream) la read non restituisce il controllo. Tornato il controllo al chiamante, questo utilizza i dati memorizzati dentro buffer (che può essere pieno o meno) e poi si rimette in ascolto finché non arriva un EOS.

Questa tecnica permette la lettura di dati di dimensione arbitraria senza necessariamente doverli caricare tutti in memoria. Ad esempio, per scaricare un file si apre uno stream in lettura sulla rete ed uno in scrittura sul disco; ogni volta che si riempe il buffer dallo stream di lettura questo viene dato in pasto allo stream di scrittura, il tutto fino all'EOS. In questo modo avremo scaricato un file di qualsiasi lunghezza occupando pochissima memoria. La dimensione del buffer va scelta in base al caso: un buffer piccolo risparmia memoria ma costringe ad un numero maggiore di letture/scritture, rallentando il processo, mentre un buffer più grande velocizza la lettura dei dati (se lo stream è "veloce") a discapito della ram.

Citazione
Ho provato ad inviare all'inputstream del testo in modo da "forzare" il metodo "read" a leggerne il contenuto

Questa frase mi suona strana. Hai aperto un input stream (quindi in lettura) dalla seriale e sei in attesa dei dati. Come fai ad inviare del testo alla inputstream? Non è possibile (AFAIK) scrivere su uno stream "spacciandosi" per il mittente.

Per quanto riguarda la prima domanda: non so risponderti, ma in questo post di StackOverflow forniscono una possibile soluzione: https://stackoverflow.com/a/808795/466938

Personalmente al posto di un thread avviato dall'activity proverei con un servizio in costante attesa a prescindere dalla disponibilità dell'activity, ma non so dirti l'efficienza dal punto di vista energetico.
Ohmnibus
Le mie app su Play Store

È stata trovata una soluzione al tuo problema? Evidenzia il post più utile premendo . È un ottimo modo per ringraziare chi ti ha aiutato.

Offline berpao

  • Utente junior
  • **
  • Post: 74
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S2
  • Play Store ID:
    Paolo Bersan
Re:Terminare un thread che usa inputstream.read() (blocking operation)
« Risposta #2 il: 03 Ottobre 2017, 12:34:35 CEST »
0
Ciao Ohmnibus,
innanzitutto grazie, ci ritroviamo ancora  :-).

Per quanto riguarda questo:

Citazione
Uno stream rappresenta un flusso di dati. Questo flusso può essere più o meno continuo in base alla situazione, e di lunghezza solitamente non nota. Per cui chi legge lo stream si mette in ascolto di dati in arrivo e, man mano che arrivano, li legge riempiendo un buffer. Finché il buffer non è pieno (o arriva un End Of Stream) la read non restituisce il controllo. Tornato il controllo al chiamante, questo utilizza i dati memorizzati dentro buffer (che può essere pieno o meno) e poi si rimette in ascolto finché non arriva un EOS.

Questa tecnica permette la lettura di dati di dimensione arbitraria senza necessariamente doverli caricare tutti in memoria. Ad esempio, per scaricare un file si apre uno stream in lettura sulla rete ed uno in scrittura sul disco; ogni volta che si riempe il buffer dallo stream di lettura questo viene dato in pasto allo stream di scrittura, il tutto fino all'EOS. In questo modo avremo scaricato un file di qualsiasi lunghezza occupando pochissima memoria. La dimensione del buffer va scelta in base al caso: un buffer piccolo risparmia memoria ma costringe ad un numero maggiore di letture/scritture, rallentando il processo, mentre un buffer più grande velocizza la lettura dei dati (se lo stream è "veloce") a discapito della ram.

direi ottima spiegazione, mi hai chiarito un po di cose.

Per questo

Citazione
Questa frase mi suona strana. Hai aperto un input stream (quindi in lettura) dalla seriale e sei in attesa dei dati. Come fai ad inviare del testo alla inputstream? Non è possibile (AFAIK) scrivere su uno stream "spacciandosi" per il mittente.

la frase ti sembra strana perchè ho detto una cavolata. Premetto che non ho lavorato granchè con le classi InputStream e OutputStream; quello che ho fatto è aver usato una istanza OutputStream che punta a "/dev/ttyHSL1" per scrivere dati, aspetttandomi che la InputStream che punta sempre a "/dev/ttyHSL1" sentisse i nuovi dati e sbloccasse il metodo read. Per fornire tutte le informazioni possibili posso dirti che le due istanze InputStream e OutputStream sono in realtà delle classi FileInputStream e FileOutputStream e che mi vengono restituite dall'oggetto che gestisce la porta seriale.

Per questo:

Citazione
Per quanto riguarda la prima domanda: non so risponderti, ma in questo post di StackOverflow forniscono una possibile soluzione: https://stackoverflow.com/a/808795/466938

l'avevo trovato ma qui usano i FileChannel. Ho fatto una prova in merito ma, a parte un problema di lettura dei dati che non ho indagato più di tanto, ho visto che il filechannel, a differenza della inputstream, non rimane in attesa sul read(), ma il ciclo continua a loopare e la cosa non mi piaceva tanto.

Per questo
Citazione
Personalmente al posto di un thread avviato dall'activity proverei con un servizio in costante attesa a prescindere dalla disponibilità dell'activity, ma non so dirti l'efficienza dal punto di vista energetico.

Se non ho capito male tu faresti un servizio che  apre il thread (una sola volta) e ogni sparata passerebbe da questo thread. Però in questo modo riuscirei ad avere un handler custom per ogni activity? Mi spiego, attualmente ogni activity che eredita dalla SerialPortActivity implementa il proprio metodo per gestire i dati letti dal barcode (questo per avere un po' di flessibilità, non so cosa mi chiederanno nel futuro gli utenti e avere in questo modo mi paro il c... ehm.. mi proteggo). Inoltre, nel caso dovessi sviluppare altre app che usano lo stesso barcode reader, ho paura che l'implementazione di un unico servizio per gestire più app complichi le cose. Però se non ho capito quello che intendo spiegami che valuto la cosa.
Grazie
P

PS
L'idea che volevo fare (leggere e scrivere sullo ipotetico file "/dev/ttyHSL1") è percorribile o ho detto una marea di cavolate?
« Ultima modifica: 03 Ottobre 2017, 12:43:39 CEST da berpao »

Offline Ohmnibus

  • Utente senior
  • ****
  • Post: 763
  • Respect: +159
    • Github
    • Google+
    • @ohmnibus
    • Mostra profilo
    • Lords of Knowledge GdR
  • Dispositivo Android:
    Huawei P9 Lite
  • Play Store ID:
    Ohmnibus
  • Sistema operativo:
    Windows 7 x64
Re:Terminare un thread che usa inputstream.read() (blocking operation)
« Risposta #3 il: 03 Ottobre 2017, 12:58:12 CEST »
+1
Citazione
Se non ho capito male tu faresti un servizio che  apre il thread (una sola volta) e ogni sparata passerebbe da questo thread. Però in questo modo riuscirei ad avere un handler custom per ogni activity? Mi spiego, attualmente ogni activity che eredita dalla SerialPortActivity implementa il proprio metodo per gestire i dati letti dal barcode (questo per avere un po' di flessibilità, non so cosa mi chiederanno nel futuro gli utenti e avere in questo modo mi paro il c... ehm.. mi proteggo). Inoltre, nel caso dovessi sviluppare altre app che usano lo stesso barcode reader, ho paura che l'implementazione di un unico servizio per gestire più app complichi le cose. Però se non ho capito quello che intendo spiegami che valuto la cosa.

La mia idea è quella di utilizzare un service privato alla tua app. Il service apre la connessione e resta in attesa*. Se esiste uno o più "listener" (ossia activity che si sono registrate) inoltra loro l'evento di lettura.
Le activity si possono collegare e registrare al service in qualsiasi momento. Quando ciò accade comunicano al service il listener da invocare. E' a carico dello sviluppatore mantenere l'elenco di listener ai quali inviare gli eventi.

Se vuoi un comportamento simile a quello attuale, nella classe base delle activity invece di aprire una connessione seriale ti connetti e ti registri al service. Per le activity derivate dovrebbe essere trasparente.

Citazione
L'idea che volevo fare (leggere e scrivere sullo ipotetico file "/dev/ttyHSL1") è percorribile o ho detto una marea di cavolate?
Si può fare ma l'effetto non è quello voluto. Avrai un InputStream che legge da quella porta, ed un OutputStream che scrive su quella porta, sono due connessioni diverse e NON un loop. Un po' come aprire due connessioni ftp per leggere dei file o per scriverli: quando scrivi un file l'ftp il server semplicemente lo memorizza senza inviarlo indietro alla connessione aperta in lettura.
Come accennato il service è privato alla tua app, quindi condiviso da tutte le activity ma non al mondo intero. Per capirci, se fai un'altra app avrai un altro service, magari identico ma che gira su un'istanza differente.


*Puoi ottimizzare chiudendo la connessione dopo la read se non ci sono listener, e riaprire la connessione quando ne viene registrato uno
Ohmnibus
Le mie app su Play Store

È stata trovata una soluzione al tuo problema? Evidenzia il post più utile premendo . È un ottimo modo per ringraziare chi ti ha aiutato.

Offline berpao

  • Utente junior
  • **
  • Post: 74
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S2
  • Play Store ID:
    Paolo Bersan
Re:Terminare un thread che usa inputstream.read() (blocking operation)
« Risposta #4 il: 05 Ottobre 2017, 08:23:39 CEST »
0
Ciao Ohmnibus,
grazie per le dritte, ora mi guardo un po' di documentazione sul service e su come funzionano, così mi rendo anche conto anche dei tempi per una eventuale implementazione (anche se, non  trovando una soluzione, non è che avrò molta scelta). Ti aggiorno
Grazie
P

Offline berpao

  • Utente junior
  • **
  • Post: 74
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S2
  • Play Store ID:
    Paolo Bersan
Re:Terminare un thread che usa inputstream.read() (blocking operation)
« Risposta #5 il: 16 Ottobre 2017, 08:09:24 CEST »
+1
Ciao, dunque alla fine ho risolto (temporaneamente) così:

Codice (Java): [Seleziona]
                public void run() {
                        try {
                                //double counter=0;
                                byte[] buffer;
                                //Log.d(TAG,"Start run()");
                                super.run();
                                while(!isInterrupted()) {

                                        //counter+=1;
                                        int size;
                                        try {
                                                if (this.inStream == null) return;

                                                if (isInterrupted()) {
                                                        throw new InterruptedException("thread blocked");
                                                }

                                                size=this.inStream.available();
                                                buffer = new byte[size];
                                                size = this.inStream.read(buffer);

                                                if (size > 1 & !isInterrupted()) {
                                                        //              Log.d(TAG,"Valid size, reading barcode....");
                                                        onDataReceived(buffer, size,true);
                                                }

                                                //Fermo l'esecuzione per 1/4 di secondo
                                                Thread.sleep(250);

                                        } catch (InterruptedException e) {
                                                Log.d(TAG,e.getMessage()+" on thread");
                                        } catch (IOException e) {
                                                Log.d(TAG,e.getMessage());
                                                e.printStackTrace();
                                                return;
                                        }
                                }
                        } catch (Exception e) {
                                Log.d(TAG,e.getMessage());
                                e.printStackTrace();
                                return;
                        }
                }

A quanto pare con il metodo "available()" l'input stream non si blocca sul metodo read(), ma prosegue il loop. Grazie al Thread.sleep() ottimizzo l'esecuzione facendo solo 4 cicli al secondo. Dico temporaneamente perchè la realizzazione di Ohmnibus è sicuramente più giusta; innanzitutto aprirei un solo thread anzichè uno per activity e non avrei il ciclo che continua a girare consumando batteria, anche se 4 cicli al secondo non sono esagerati (in realtà devo ancora fare dei test in tal senso per vedere il consumo della batteria). La realizzazione del service però mi risulta più onerosa dal punto di vista del tempo (ci sono un po di casistiche che qui non ho descritto da gestire), quindi aspetto tempi migliori  ;-).
Per il momento chiudo.
Ciao e grazie
P