How to make an async listener do blocking?

1k Views Asked by At

I am writing a blackberry app that communicates with a simple Bluetooth peripheral using text based AT commands - similar to a modem... I can only get it working on the blackberry using an event listener. So the communication is now asynchronous.

However, since it is a simple device and I need to control concurrent access, I would prefer to just have a blocking call.

I have the following code which tries to convert the communications to blocking by using a wait/notify. But when I run it, notifyResults never runs until getStringValue completes. i.e. it will always timeout no matter what the delay.

The btCon object runs on a separate thread already.

I'm sure I am missing something obvious with threading. Could someone kindly point it out?

Thanks

I should also add the the notifyAll blows up with an IllegalMonitorStateException.

I previously tried it with a simple boolean flag and a wait loop. But the same problem existed. notifyResult never runs until after getStringValue completes.

public class BTCommand implements ResultListener{
    String cmd;
    private BluetoothClient btCon;
    private String result;

    public BTCommand (String cmd){
        this.cmd=cmd;
        btCon = BluetoothClient.getInstance();
        btCon.addListener(this);

        System.out.println("[BTCL] BTCommand init");
    }

    public String getStringValue(){
        result = "TIMEOUT";
        btCon.sendCommand(cmd);
        System.out.println("[BTCL] BTCommand getStringValue sent and waiting");

        synchronized (result){
            try {
                result.wait(5000);
            } catch (InterruptedException e) {
                System.out.println("[BTCL] BTCommand getStringValue interrupted");
            }
        }//sync
        System.out.println("[BTCL] BTCommand getStringValue result="+result);

        return result;
    }

    public void notifyResults(String cmd) {
        if(cmd.equalsIgnoreCase(this.cmd)){
            synchronized(result){
                result = btCon.getHash(cmd);
                System.out.println("[BTCL] BTCommand resultReady: "+cmd+"="+result);                
                result.notifyAll();
            }//sync
        }
    }

}
3

There are 3 best solutions below

0
On

The code will work ok. If you will use final object instead of string variable. I'm surprised that you don't get NPE or IMSE.

Create field:

private final Object resultLock = new Object();

Change all synchronized sections to use it instead of string field result.

I don't like magic number 5 sec. I hope you treat null result as timeout in your application.

2
On

It may be more appropriate to use a Latch, Semaphore, or a Barrier, as recommended by Brian Goetz book Java Concurrency in Practice.

These classes will make it easier to write blocking methods, and will likely help to prevent bugs, especially if you are unfamiliar with wait() and notifyAll(). (I am not suggesting that YOU are unfamiliar, it is just a note for others...)

0
On

Since both notifyResults and getStringValue have synchronized clauses on the same object, assuming getStringValues gets to the synchronized section first notifyResults will block at the start of the synchronized clause until getStringValues exits the synchronized area. If I understand, this is the behaviour you're seeing.

Nicholas' advice is probably good, but you may not find any of those implementations in BlackBerry APIs you're using. You may want to have a look at the produce-consumer pattern.