Reputation: 6242
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
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
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