2
votes

I'm looking for the Java/Akka equivalent of Python's yield from or gevent's monkey patch.


Update

There has been some confusion in the commets about what is question is asking so let me restate the question:

If I have a future, how do I wait for the future to compete without blocking the thread AND without returning to caller until the future is complete?


Lets say we have method that blocks:

public Object foo() {
    Object result = someBlockingCall();
    return doSomeThingWithResult(result);
}

To make this asynchronous, we would pass SomeBlockingCall() a callback:

public void foo() {
   someAsyncCall(new Handler() {
        public void onSuccess(Object result) {
            message = doSomethingWithResult(result);
            callerRef.tell(message, ActorRef.noSender());
        }
    });
}

The call to foo() now returns before the result is ready, so the caller no longer gets the result. We have to get the result back to the caller by passing a message. To convert synchronous code to asynchronous Akka code, a redesign of the caller is required.

What I'd like is async code that looks like synchronous code like Python's Gevent.

I want to write:

public Object foo() {
    Future future = someAsyncCall();

    // save stack frame, go back to the event loop and relinquish CPU
    // so other events can use the thread,
    // and come back when future is complete and restore stack frame
    return yield(future);
}

This would allow me to make my synchronous code asynchronous without a redesign.

Is this posible?

Note: The Play framework seems to fake this with async() and AsyncResult. But this won't work in general since I have to write the code that handles the AsyncResult which would look like the above callback handler.

2
Yes, just use a Future? CompleteableFuture.runAsync ought to do it. - Boris the Spider
Use ExecutorCompletionService. - M.K.
@Boris the Spider, No. All a future does is allow the event handler to be moved to the caller. The problem is not solved since the caller still has the problem of having nothing to do until the handler is called. The whole point is to not block during this time. - Jay
Java has no yield construct, so all you can do is use a callback. Now that callback can be registered directly on the CompletableFuture and it will be invoked automatically as and when. This way you set up an asynchronous pipeline and carry on with whatever you were doing. So you can do CompleteableFuture.runAsync(this::myLongTask).thenAccept(this::happensAfter). - Boris the Spider
@I.K., If ExecutorCompletionService.take() relinquishes the thread/CPU without blocking, as described above, then this will work. However, I'm not able to confirm this from the docs. take() appears to be a blocking call. - Jay

2 Answers

3
votes

I think trying to get back a more straightforward sync design, although an efficient one, is actually a good intention and a good idea (see for example here).

Quasar has facilities to obtain sync/blocking APIs that are still highly efficient from async APIs (see this blog post), which looks exactly what you're looking for.

The fundamental problem is not that the sync/blocking style itself is bad (actually async and sync are dual styles and can be transformed into one another, see for example here), but rather than blocking Java's heavyweight threads is not efficient: it is not an abstraction problem but an implementation problem, so instead of giving up the easier thread abstraction only because the implementation is inefficient, I agree it is better for the future of your code to try and look for more efficient thread implementations.

As Roland hinted, Quasar adds lightweight threads or fibers to the JVM, so you can get the same performance of async frameworks without giving up the thread abstraction and regular imperative control flow constructs (sequence, loops etc.) available in the language.

It also unifies JVM/JDK's threads and its fibers under a common strand interface, so they can interoperate seamlessly, and provides a porting of java.util.concurrent to this unified concept.

On top of strands (either fibers or regular threads) Quasar also offers fully-fledged Erlang-style actors, blocking Go-like channels and dataflow programming, so you can choose the concurrent programming paradigm that suits best your skills and needs without being forced into one.

It also provides bindings for popular and standard technologies (as part of the Comsat project), so you can preserve your code assets because the porting effort will be minimal (if any). For the same reason you can also opt-out easily, should you choose to.

Currently Quasar has binding for Java 7 and 8, Clojure under the Pulsar project and soon JetBrain's Kotlin. Being based on JVM bytecode instrumentation, Quasar can really work with any JVM language if an integration module is present, and it offers tools to build additional ones.

1
votes

The answer to your question is “no”, and that is very much by design. Writing a method to be asynchronous means returning the Future as its result, the method itself will not perform the computation but will arrange for the result to be provided later. You can then pass this Future to the right place where it is further used, for example by transforming it using one of the many combinators (like map, recover, etc.).

Awaiting a strict result for the Future will have to block the current thread of execution, no matter which technology you use. With plain JVM threads you will block a real thread from the operating system, with Quasar you will block your Fiber, with Akka you will block your Actor (*); blocking means blocking, no way around that.


(*) In an Actor you would get the result via a message at a later point, and until that point you will have to switch the behavior such that new incoming messages are stashed, rejected or dropped, depending on your use-case.