Domanda

Ho scritto un'API Bluetooth per la connessione con un accessorio esterno. Il modo in cui l'API è progettata è che ci sono un sacco di chiamate bloccanti come getTime, setTime, getVolume, setVolume, ecc. Il modo in cui questo lavoro è che creano un carico utile da inviare e chiamare un metodo chiamato sendAndReceive() che fa un po 'di preparazione e alla fine fa quanto segue:

byte[] retVal = null;
BluetoothSocket socket = getSocket();
// write
socket.getOutputStream().write(payload);
// read response
if(responseExpected){
    byte[] buffer = new byte[1024]; // buffer store for the stream
    int readbytes = socket.getInputStream().read(buffer);
    retVal = new byte[readbytes];
    System.arraycopy(buffer, 0, retVal, 0, readbytes);
}
return retVal;

Il problema è che a volte questo dispositivo diventa lento o non reattivo, quindi vorrei mettere un timeout in questa chiamata. Ho provato diversi metodi per mettere questo codice in un thread Future Task ed eseguirlo con un timeout, ad esempio:

FutureTask<byte[]> theTask = null;
// create new task
theTask = new FutureTask<byte[]>(
        new Callable<byte[]>() {

            @Override
            public byte[] call() {
                byte[] retVal = null;
                BluetoothSocket socket = getSocket();
                // write
                socket.getOutputStream().write(payload);
                // read response
                if(responseExpected){
                    byte[] buffer = new byte[1024]; // buffer store for the stream
                    int readbytes = socket.getInputStream().read(buffer);
                    retVal = new byte[readbytes];
                    System.arraycopy(buffer, 0, retVal, 0, readbytes);
                }
                return retVal;
            }
        });

// start task in a new thread
new Thread(theTask).start();

// wait for the execution to finish, timeout after 6 secs
byte[] response;
try {
    response = theTask.get(6L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    throw new CbtException(e);
} catch (ExecutionException e) {
    throw new CbtException(e);
} catch (TimeoutException e) {
    throw new CbtCallTimedOutException(e);
}
    return response;
}

Il problema con questo approccio è che non riesco a rimborsare le eccezioni nel metodo di chiamata e poiché alcuni dei metodi lanciano eccezioni, voglio tornare al cliente dell'API, non posso usare questa metodologia.

Mi può consigliare qualche altra alternativa? Grazie!

È stato utile?

Soluzione

Perché non provare qualcosa di simile

public class ReadTask extends Thread {
  private byte[] mResultBuffer;
  private Exception mCaught;
  private Thread mWatcher;
  public ReadTask(Thread watcher) {
    mWatcher = watcher;
  }

  public void run() {
    try {
      mResultBuffer = sendAndReceive();
    } catch (Exception e) {
      mCaught = e;
    }
    mWatcher.interrupt();
  }
  public Exception getCaughtException() {
    return mCaught;
  }
  public byte[] getResults() {
    return mResultBuffer;
  }
}

public byte[] wrappedSendAndReceive() {
  byte[] data = new byte[1024];
  ReadTask worker = new ReadTask(data, Thread.currentThread());

  try {
    worker.start();
    Thread.sleep(6000);
  } catch (InterruptedException e) {
    // either the read completed, or we were interrupted for another reason
    if (worker.getCaughtException() != null) {
      throw worker.getCaughtException();
    }
  }

  // try to interrupt the reader
  worker.interrupt();
  return worker.getResults;
}

C'è un caso Edge qui che il thread chiama wrappedSendAndReceive() può essere interrotto per qualche motivo diverso dall'interrupt dal readask. Suppongo che un bit fatto possa essere aggiunto al Readtask per consentire all'altro thread di testare se la lettura è finita o l'interrupt è stato causato da qualcos'altro, ma non sono sicuro di quanto sia necessario.

Un'ulteriore nota è che questo codice contiene la possibilità di perdita di dati. Se i 6 secondi scadono e una quantità di dati è stata letta, questo finirà per essere scartato. Se volessi aggirarlo, dovresti leggere un byte alla volta in readask.run () e quindi catturare in modo appropriato l'interruzione. Ciò richiede ovviamente un po 'di rielaborazione del codice esistente per mantenere un contatore e ridimensionare in modo appropriato il buffer di lettura quando viene ricevuto l'interrupt.

Altri suggerimenti

Stai salvando, non puoi usare il metodo Future <> perché vuoi rimodellare l'eccezione, ma in realtà questo è possibile.

La maggior parte degli esempi online implementano callabili con il prototipo public ? call() Ma cambialo in public ? call() throws Exception E andrà tutto bene: otterrai l'eccezione nella chiamata thetask.get () e puoi ritoccala ai chiamanti.

Ho usato personalmente gli esecutori esattamente per la gestione del timeout socket Bluetooth su Android:

protected static String readAnswer(...)
throws Exception {
    String timeoutMessage = "timeout";
    ExecutorService executor = Executors.newCachedThreadPool();
    Callable<String> task = new Callable<String>() {
       public String call() throws Exception {
          return readAnswerNoTimeout(...);
       }
    };
    Future<String> future = executor.submit(task);
    try {
       return future.get(SOCKET_TIMEOUT_MS, TimeUnit.MILLISECONDS); 
    } catch (TimeoutException ex) {
        future.cancel(true);
        throw new Exception(timeoutMessage);
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top