Pregunta

He escrito una API Bluetooth para conectarme con un accesorio externo. La forma en que se diseña la API es que hay un montón de llamadas de bloqueo como getTime, setTime, getVolume, setVolume, etc. La forma en que estos funcionan es que crean una carga útil para enviar y llamar a un método llamado sendAndReceive() que hace un trabajo de preparación y eventualmente hace lo siguiente:

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;

El problema es que a veces este dispositivo se vuelve lento o no sensible, por lo que me gustaría poner un tiempo de espera en esta llamada. He probado varios métodos para poner este código en una tarea de hilo futura y ejecutarlo con un tiempo de espera, por ejemplo:

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;
}

El problema con este enfoque es que no puedo rehacer excepciones en el método de llamadas y, dado que algunos de los métodos arrojan excepciones, quiero volver al cliente de la API, no puedo usar esta metodología.

¿Me puede recomendar alguna otra alternativa? ¡Gracias!

¿Fue útil?

Solución

¿Por qué no probar algo como

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;
}

Hay un caso de borde aquí que la llamada del hilo wrappedSendAndReceive() puede ser interrumpido por alguna razón que no sea la interrupción de la tarta de lectura. Supongo que se podría agregar un bit hecho a la tarta de lectura para permitir que el otro hilo pruebe si la lectura terminó o la interrupción fue causada por algo más, pero no estoy seguro de cuán necesario es esto.

Otra nota es que este código contiene la posibilidad de pérdida de datos. Si los 6 segundos vencen y se leen cierta cantidad de datos, esto terminará siendo descartado. Si quisiera trabajar en torno a esto, necesitaría leer un byte a la vez en ReadTask.run () y luego atrapar adecuadamente la Excepción InterruptedException. Obviamente, esto requiere un poco de retrabajo del código existente para mantener un contador y cambiar el tamaño adecuado del búfer de lectura cuando se recibe la interrupción.

Otros consejos

Estás guardando que no puede usar el método Future <> porque desea volver a retirar la excepción, pero de hecho esto es posible.

La mayoría de los ejemplos en línea implementan llamables con el prototipo public ? call() Pero solo cámbielo a public ? call() throws Exception Y todo estará bien: obtendrá la excepción en la llamada thetask.get () y puede volver a colocarla en las personas que llaman.

He usado personalmente ejecutores exactamente para el manejo del tiempo de espera de Bluetooth Socket en 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);
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top