Sergey Alaev
Sergey Alaev

Reputation: 3982

NIO.2 asynchronous channels coding guidelines

For example, i want to read 100500 bytes to an array:

byte[] array = new byte[100500];
int offset = 0;
ByteBuffer buf = ByteBuffer.directBuffer(4096);

channel.read(buffer, null, new LambdaAdapter((count, exception, attachment) -> {
    buf.get(array, offset, count);
    offset += count; //will not work, offset must be final
    if (offset < 100500) {
        channel.read(buffer, null, /*what here? must be lambda we are currently in but we can't use it!*/)
    }
    //here we have our 100500 bytes inside array
});

LambdaAdapter here is simple wrapper that converts CompletionHandler to functional interface with three arguments.

Anyway. Offset can be put to 'attachment' parameter, lambda can be declared beforehand and reused as as field. However, resulting code is always ugly Ugly UGLY.

I was not able to write acceptable solution even for such simple task - what it will look like for complex protocol where reads are interleaved with writes and wrapped in complex logic?

Does anyone know suitable way to deal with async API? If you think that Scala can save the world here, feel free to use it.

Upvotes: 0

Views: 336

Answers (2)

Sergey Alaev
Sergey Alaev

Reputation: 3982

I found acceptable solutions. First of all, following worth looking at: https://github.com/Netflix/RxJava

As for coding guidelines...

  • Async method is a method that returns before doing any useful work.
  • Async operation code should start with creation of new object, let's call it context

    Void startMyAsync(String p1, int p2, Observer callback) {
         return new MyAsyncContext(p1, p2, callback).start();
    }
    
  • Result of async operation method is not used - let's return Void type. It is useful since compiler will check for you that every async method calls another async method or explicitly returns null.

  • Async methods can throw exceptions
  • Async callbacks should not throw exceptions - callback error handler must be used instead
  • Async callbacks should only contain try...catch and context method invocation.
  • Async callbacks should return Void as well
  • Additional data provided by CompletionHandler is not needed - context fields should be used instead. If async flow does not split synchronization is not needed.

Example of async callback:

return myAsyncMethod(param1, param2, (result, exc, att) -> {
    try {
        if (exc != null) {
            return handleError(exc); //cleanup resources and invoke parentHandler.complete(null, exc, null)
        } else {
            return handleResult(result);
        }
    } catch (Exception e) {
        return handleError(exc); //cleanup resources and invoke parentHandler.complete(null, exc, null)
    }
});

Upvotes: 0

Alexei Kaigorodov
Alexei Kaigorodov

Reputation: 13535

I know how to deal with with async computations IO in general, and async IO in particular. Async program should be represented as a dataflow graph, as described in Dataflow_programming. Each node has a set of inputs, each input accepts messages or signals, and fires (goes to a thread pool) when all inputs are filled. For example, a node representing a socket channel has two inputs: one for ByteBuffers and one to indicate that the channel is free and can accept IO requests. So a node resembles a function in functional programming, but can be reused - no new objects are created for next IO operation.

Scala (and Akka) actors does not fit, as each actor has only one input. Look, however, at Scala Dataflow - I did not learn it yet, but the name is promising :).

I have developed a dataflow library for java dataflow library for java, but its IO part is somewhat outdated (I am thinking of more elegant, or at least less ugly API). Look at echo server implementation in examples subdirectory.

Upvotes: 1

Related Questions