keldar
keldar

Reputation: 6242

Java - synchronous callback

I have the following code which is executed asynchronously. I would like to make it synchronous in order to follow some logical flow but I cannot work out how.

You will see that scanning is set to true to indicate that the method is still working, at the beginning - I then initiate a findPrinters(...) command - this contains a DiscoveryHandler which runs asynchronously - foundPrinter() is called each time an item is discovered. discoveryFinished() is when the discovery process is successfully completed, and discoveryError(...) is called whenever an error occurs.

I rely on something being set in my DiscoveryHandler before I would like to return from this method. Hence why I have while (scanning) underneath it. But this feels like a hack to me, and not the correct way of doing things. I cannot get wait() and notify() working. Can someone tell me what the correct way to do this is please?

private boolean findPrinter(final Context ctx) {
    try {
        scanning = true;
        BluetoothDiscoverer.findPrinters(ctx, new DiscoveryHandler() {

            public void foundPrinter(DiscoveredPrinter device) {
                if (device instanceof DiscoveredPrinterBluetooth) {
                    DiscoveredPrinterBluetooth btDevice = (DiscoveredPrinterBluetooth) device;

                    if (btDevice.friendlyName.startsWith("XXXX")) {
                        try {
                            connection = new BluetoothConnection(btDevice.address);
                            connection.open();

                            if (connection.isConnected()) {
                                address = btDevice.address;
                            }
                        } catch (Exception ex) {

                        }
                    }
                }
            }

            public void discoveryFinished() {
                scanning = false;
            }

            public void discoveryError(String arg0) {
                scanning = false;
            }
        });
    } catch (Exception ex) {

    }

    while (scanning) {}

    return false;
}

Upvotes: 5

Views: 5718

Answers (2)

Andrey Chaschev
Andrey Chaschev

Reputation: 16476

You could do this with CountDownLatch, which might be the lightest synchronization primitive in java.util.concurrent:

private boolean findPrinter(final Context ctx) {
    final CountDownLatch latch = new CountDownLatch(1);
    final boolean[] result = {false};

    ...

    BluetoothDiscoverer.findPrinters(ctx, new DiscoveryHandler() {

        ...

        public void discoveryFinished() {
            result[0] = true;
            latch.countDown();
        }
    
        public void discoveryError(String arg0) {
            result[0] = false;
            latch.countDown();
        }

        ...
    }

    // before final return
    // wait for 10 seconds for the response
    latch.await(10, TimeUnit.SECONDS);

    //return the result, it will return false when there is timeout
    return result[0];
}

Upvotes: 6

SamYonnou
SamYonnou

Reputation: 2068

There are a bunch of ways you can do this and wait()/notify() is probably not the best since you probably want to return something from your async method. As such I suggest using something like a BlockingQueue. Here is a simplified example of how you can do this:

private boolean findPrinter(final Context ctx) {
    final BlockingQueue<?> asyncResult = new SynchronousQueue<?>();
    try {
        BluetoothDiscoverer.findPrinters(ctx, new DiscoveryHandler() {

            public void foundPrinter(DiscoveredPrinter device) {
                if (device instanceof DiscoveredPrinterBluetooth) {
                    DiscoveredPrinterBluetooth btDevice = (DiscoveredPrinterBluetooth) device;

                    if (btDevice.friendlyName.startsWith("XXXX")) {
                        try {
                            connection = new BluetoothConnection(btDevice.address);
                            connection.open();

                            if (connection.isConnected()) {
                                address = btDevice.address;
                            }
                        } catch (Exception ex) {

                        }
                    }
                }
            }

            public void discoveryFinished() {
                asyncResult.put(true);
            }

            public void discoveryError(String arg0) {
                asyncResult.put(arg0);
            }
        });
    } catch (Exception ex) {
    }

    Object result = asyncResult.take();

    if (result instanceof Boolean) {
        return (Boolean) result;
    } else if (result instanceof String) {
        logError((String) result);
    }

    return false;
}

One problem with using SynchronousQueue here though is that if discoveryFinished()/discoveryError() is called more than once, then the thread executing the code asynchronously will block forever since the SynchronousQueue assumes there will be exactly one take() per every put() and will block if a put() is made without a corresponding take() or vice versa. So if in your case those methods can be called more than once you would probably use a different kind of BlockingQueue instead (see documentation).

Upvotes: 3

Related Questions