some async interface advice

96 views
Skip to first unread message

Daniel Davidson

unread,
Mar 18, 2015, 11:55:29 AM3/18/15
to mi...@dartlang.org

Below are some class definitions I'm working on. I'm trying to get a better feel for async programming in Dart so any comments/suggestions/criticisms appreciated. It seems historically code used to be much more synchronous and as things developed techniques for adding asynchronous code have come along. But it is not always clear how to mesh the two worlds. Since I'm trying to learn I'm playing with thrift, which I am new to and also want to learn about. So I would like to try over time to add support for dart. If it already exists or is in the works - please let me know.

The Java version has the following relevant read methods for a transport:

/**
 * Reads up to len bytes into buffer buf, starting at offset off.
 *
 * @param buf Array to read into
 * @param off Index to start reading at
 * @param len Maximum number of bytes to read
 * @return The number of bytes actually read
 * @throws TTransportException if there was an error reading data
 */
public abstract int read(byte[] buf, int off, int len)
  throws TTransportException;

/**
 * Guarantees that all of len bytes are actually read off the transport.
 *
 * @param buf Array to read into
 * @param off Index to start reading at
 * @param len Maximum number of bytes to read
 * @return The number of bytes actually read, which must be equal to len
 * @throws TTransportException if there was an error reading data
 */
public int readAll(byte[] buf, int off, int len)
  throws TTransportException

Obviously both calls are synchronous, but they feel different since the first is intended to read whatever is available up to len and return immediately and the latter wants to block until all is read.

Is it the case that once you enter into the async realm and are designing interfaces it is safest to return Futures? Because if an implementation needs to use any function that returns a future then somehow a chain of them needs to make its way out of the method.

How does the following look as a general base for the read side of a transport? I think a more Dartish version based on streams would be more fun and educational, but I imagine the leverage of working like other languages do has value too.

A question I have is readAll needs to only return when all desired bytes are read. readByte is simply a utiltiy function, but does it open the door for race issues? If there are two calls that are not chained, is there a chance the second will interfere with the first since they both are touching the _oneByeData member?

transport.readByte().then((b) = print(b));
transport.readByte().then((b) = print(b));    
/// A transport reader.
///
/// Provides methods to read from a transport and store read data in client
/// allocated ByteData objects.
///
abstract class TReader {

  /// Read from transport, storing read data in [target] and complete
  /// only when desired amount read.
  ///
  /// [offsetInBytes] - Where in [target] to begin storing read data
  ///
  /// [length] - Number of bytes to attempt to read - if not set will
  ///            attempt to read as much as [target] has from [offsetInBytes]
  ///            to its end
  ///
  /// Returns when number of bytes read - which *will* equal requested
  /// amount considering defaults of [offsetInBytes] and [length]
  Future<int> readAll(ByteData target, [int offsetInBytes = 0, int length]);

  /// Try to read from transport storing byte data in [target] and complete
  /// as soon as possible.
  ///
  /// [offsetInBytes] - Where in [target] to begin storing read data
  ///
  /// [length] - Number of bytes to attempt to read - if not set will
  ///            attempt to read as much as [target] has from [offsetInBytes]
  ///            to its end
  ///
  /// Returns number of bytes read, which might be less than desired
  Future<int> tryRead(ByteData target, [int offsetInBytes = 0, int length]);

  /// Read a byte and complete future when byte is read
  /// Returns byte read
  Future<int> readByte() =>
      readAll(_oneByteData).then((_) => _oneByteData.getInt8(0));

  /// Buffer for reading the next byte
  ByteData _oneByteData = new Uint8List(1).buffer.asByteData();
}

The writer:

/// A transport writer
abstract class TWriter {
  /// Write data from [source] to the transport

  /// [offsetInBytes] - Where in [source] to begin reading data to be
  /// written
  ///
  /// [length] - Number of bytes to write - if not set will attempt to
  ///            write as much as [source] has from [offsetInBytes] to
  ///            its end
  Future write(ByteData source, [int offsetInBytes = 0, int length]);

  /// Flush any pending writes
  Future flush();
}

And the combined Transport:

abstract class TTransport extends Object with TReader implements TWriter {...}

Or as Lasse and Gunter point out maybe composition is preferable? Most other languages I've seen use inheritance but I don't know that it is required.

abstract class TTransport {
    TReader get reader;
    TWriter get writer;
}

Daniel Davidson

unread,
Mar 30, 2015, 5:01:11 PM3/30/15
to mi...@dartlang.org
I'm just following up to see if anyone had suggestions, comments. Maybe this was too much of a "tl;dr" :-)

Thanks
Dan

Warren

unread,
Mar 30, 2015, 8:19:49 PM3/30/15
to mi...@dartlang.org


Hi Dan

Dart has always been async from the start, and as you are  discovering,  async programming is infectious. Once you have a little async - it propagates through your entire codebase. 

Your question on thrift is fairly broad. I'd suggest reviewing the async/await articles, and then have a look at an implementation of something that is similar in intent to thrift. Perhaps https://github.com/dart-lang/rpc
Reply all
Reply to author
Forward
0 new messages