Search code examples
javaconcurrencyasynchronousblocking

Wrapping an asynchronous computation into a synchronous (blocking) computation


similar questions:

I have an object with a method I would like to expose to library clients (especially scripting clients) as something like:

interface MyNiceInterface
{
    public Baz doSomethingAndBlock(Foo fooArg, Bar barArg);
    public Future<Baz> doSomething(Foo fooArg, Bar barArg);
    // doSomethingAndBlock is the straightforward way;
    // doSomething has more control but deals with
    // a Future and that might be too much hassle for
    // scripting clients
}

but the primitive "stuff" I have available is a set of event-driven classes:

interface BazComputationSink
{
    public void onBazResult(Baz result);
}

class ImplementingThing
{
    public void doSomethingAsync(Foo fooArg, Bar barArg, BazComputationSink sink);
}

where ImplementingThing takes inputs, does some arcane stuff like enqueueing things on a task queue, and then later when a result occurs, sink.onBazResult() gets called on a thread that may or may not be the same thread as ImplementingThing.doSomethingAsync() was called.

Is there a way I can use the event-driven functions I have, along with concurrency primitives, to implement MyNiceInterface so scripting clients can happily wait on a blocking thread?

edit: can I use FutureTask for this?


Solution

  • Using your own Future implemenation:

    public class BazComputationFuture implements Future<Baz>, BazComputationSink {
    
        private volatile Baz result = null;
        private volatile boolean cancelled = false;
        private final CountDownLatch countDownLatch;
    
        public BazComputationFuture() {
            countDownLatch = new CountDownLatch(1);
        }
    
        @Override
        public boolean cancel(final boolean mayInterruptIfRunning) {
            if (isDone()) {
                return false;
            } else {
                countDownLatch.countDown();
                cancelled = true;
                return !isDone();
            }
        }
    
        @Override
        public Baz get() throws InterruptedException, ExecutionException {
            countDownLatch.await();
            return result;
        }
    
        @Override
        public Baz get(final long timeout, final TimeUnit unit)
                throws InterruptedException, ExecutionException, TimeoutException {
            countDownLatch.await(timeout, unit);
            return result;
        }
    
        @Override
        public boolean isCancelled() {
            return cancelled;
        }
    
        @Override
        public boolean isDone() {
            return countDownLatch.getCount() == 0;
        }
    
        public void onBazResult(final Baz result) {
            this.result = result;
            countDownLatch.countDown();
        }
    
    }
    
    public Future<Baz> doSomething(Foo fooArg, Bar barArg) {
        BazComputationFuture future = new BazComputationFuture();
        doSomethingAsync(fooArg, barArg, future);
        return future;
    }
    
    public Baz doSomethingAndBlock(Foo fooArg, Bar barArg) {
        return doSomething(fooArg, barArg).get();
    }
    

    The solution creates a CountDownLatch internally which is cleared once the callback is received. If the user calls get, the CountDownLatch is used to block the calling thread until the computation completes and call the onBazResult callback. The CountDownLatch will assure that if the callback occurs before get() is called the get() method will return immediately with a result.