2

How can I "convert" a blocking method call to a CompletableFuture? Example:

T waitForResult() throws InterruptedException {
    obj.await(); // blocking call
    // ...
    return something;
}

I need to turn that into this:

CompletableFuture.of(this::waitForResult); // .of(Callable<T>) doesn't exist

Some things to consider:

  1. waitForResult() may throw exceptions. These have to be handled correctly, so that completableFuture.get() would throw an InterruptedException or an ExecutionException.
  2. There must not be another thread involved (supplyAsync() would do so).
  3. It must be a CompletableFuture (possibly wrapped).

I tried this, but this won't handle exceptions correctly:

CompletableFuture.completedFuture(Void.TYPE).thenApply(v -> {
    try {
        listener.await();
        // ...
        return listener.getResult();
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } catch (SnmpException e) {
        throw new RuntimeException(e);
    }
});

I know Create CompletableFuture from a sync method call, but it doesn't help me:

  • The original code in the question blocks the main thread
  • The code in the answers either incorporates a third thread or doesn't handle exceptions correctly (correct me if I'm wrong)
13
  • Do I have to subclass CompletableFuture?
    – steffen
    Commented Jun 7, 2019 at 11:39
  • Can you clarify about point 2. , "there must be no other thread involved"? Which thread should call (and wait on) obj.await() ? If it is the thread running main; then you block the main thread. Otherwise, you need to involve another thread. And in this case, supplyAsynch will probably be ok
    – Daniele
    Commented Jun 7, 2019 at 11:41
  • @Daniele There's a main thread building the CompletableFuture, a second thread that later calls get() which in turns runs the callable (resulting in listener.await()). A third thread would be started when using supplyAsync().
    – steffen
    Commented Jun 7, 2019 at 11:59
  • So you want the "main" thread to create the CompletableFuture; you have a second thread calling future.get(); and as part of calling get(), that second thread will also execute listener.await(). Is that right?
    – Daniele
    Commented Jun 7, 2019 at 12:58
  • There seems to be a fundamental misunderstanding. Calling get() does not cause the callable to be invoked. Calling get() only makes the caller waiting for the result, whether there is an attempt to provide it or not. When your main thread is supposed to call listener.await(), it must do so; it will not know whether anyone has invoked get() on the future or not. — Besides that, when the second thread is supposed to call get() on the future created by the first one, the first thread must somehow hand over the future to the second thread.
    – Holger
    Commented Jun 7, 2019 at 13:30

3 Answers 3

1

I'm not sure I understand your requirements. Does this meet them?

private <T> CompletableFuture<T> supplySynchronously(Callable<T> callable) {
    CompletableFuture<T> f = new CompletableFuture<>();
    try {
        f.complete(callable.call());
    } catch (Exception e) {
        f.completeExceptionally(e);
    }
    return f;
}
1
  • 1
    Here, the main thread building the CompletableFuture will get blocked because it calls callable.call() itself.
    – steffen
    Commented Jun 7, 2019 at 12:02
1

You can try this, its an big abuse of CompletableFuture but you have to decide if its acceptable for your use case:

private static <T> CompletableFuture<T> supplySynchronously(Callable<T> callable) {
    CompletableFuture<T> f = new CompletableFuture() {

        public T get() throws InterruptedException, ExecutionException {
            synchronized (callable) {
                if (!isDone()) {
                    try {
                        T result = callable.call();
                        complete(result);
                    } catch (Exception e) {
                        completeExceptionally(e);
                    }

                }
            }
            return (T) super.get();
        }
    };
    return f;
}
8
  • That's what I was expecting: I have to subclass CompletableFuture. I'm just very unsure if overriding get() is the correct way to do so. Your code seems correct to me. (You could use an extra Lock object instead of synchronizing on the Callable.)
    – steffen
    Commented Jun 7, 2019 at 13:52
  • Why do you think this is a big abuse?
    – steffen
    Commented Jun 7, 2019 at 13:52
  • 1
    Yes, its better to use extra lock instead of locking on callable(someone else can accidentally lock on that object also). I mean abuse in sense that the creator of that class didnt expect that we will use it in this way. I think we have to override get() method, to mimic the same behaviour as normal Completable future in sense that the callable will be called only once and either result is set or excpetion is set. Also if someone cancel the future before it works as expected.
    – HPCS
    Commented Jun 7, 2019 at 13:59
  • I agree. That's why I was searching for an alternative to subclassing CompletableFuture. Here, for example, isDone() doesn't work, because get() has to be called first to produce a result.
    – steffen
    Commented Jun 7, 2019 at 14:04
  • I am not sure if I understand, I think you have to call isDone() otherwise it would be possible to call the callable two times.
    – HPCS
    Commented Jun 7, 2019 at 14:11
1

Let the method Listener.await() invokes method CountDownLatch.await():

class Listener {
   CountDownLatch latch = new CountDownLatch(counter);

   void someMethod(){
     latch.countdown();
   }

   public void await() {
      latch.await();
   }
}

then you can convert it to an asynchronous in following way:

class Listener {
   AsynCountDownLatch latch = new AsynCountDownLatch(counter);

   void someMethod(){ // here nothing changed
     latch.countdown();
   }

   public CompletableFuture<Void> async() {
      return latch.fin;
   }
}

class AsynCountDownLatch extends AsynCountDownLatch {
   CompletableFuture<Void> fin = new CompletableFuture<>();

   public AsynCountDownLatch(long counter) {
     super(counter);
   }

   public void countdown() {
       super.countdown();
       if (super.getCount()==0L) {
           fin.complete(null);
       }
   }
 }

UPDT: if the listener uses another class, then that class also has to be modified/extended/replaced to convert blocking operations to non-blocking. There is no universal way to do such a conversion.

1
  • Yes, I think this is also possible! I'll check that when I'm back at work.
    – steffen
    Commented Jun 7, 2019 at 17:17

Not the answer you're looking for? Browse other questions tagged or ask your own question.