Overriding Stream Asynchrony

Overriding Stream Asynchrony

Rate This
  • Comments 6

In .NET 4.5 Beta, the Stream class provides multiple virtual methods related to reading and writing:

  • Read, BeginRead / EndRead, ReadAsync
  • Write, BeginWrite / EndWrite, WriteAsync
  • Flush, FlushAsync
  • CopyToAsync

As a developer deriving from Stream, it’s helpful to understand what the base implementations do and when you can and should override them.

Read, Write, Flush

The Read, Write, and Flush methods are the core synchronous mechanisms from reading and writing from and to a stream:

public abstract int Read(  byte[] buffer, int offset, int count);
public abstract void Write(byte[] buffer, int offset, int count);
public abstract void Flush();

You must override these methods. This isn’t just guidance: the methods are abstract, and thus all valid derivations must override them.  If your stream is only readable or only writable, or if you wanted to prohibit synchronous usage, you could choose to throw a NotSupportedException from the methods you don’t want consumers to be using, but you still must override the methods.

BeginRead/EndRead, BeginWrite/EndWrite

Since the early days of .NET, Stream has supported asynchronous reading and writing via the following methods that conform to the APM pattern:

public virtual IAsyncResult BeginRead(
    byte[] buffer, int offset, int count, AsyncCallback callback, object state);
public virtual int EndRead(IAsyncResult asyncResult);

public virtual IAsyncResult BeginWrite(
    byte[] buffer, int offset, int count, AsyncCallback callback, object state);
public virtual void EndWrite(IAsyncResult asyncResult);

If you don’t override BeginRead/EndRead or BeginWrite/EndWrite, their default implementation in the Stream class will simply queue up work to the ThreadPool to call the corresponding synchronous Read/Write method. This means that, by default, these Begin/End methods will be doing their work asynchronously with regards to the caller, but for the duration of the actual read or write operation, a thread from the ThreadPool will be blocked (see my previous blog post on offloading vs scalability). If you can provide a truly asynchronous implementation, one based on async I/O that won’t block threads while I/O operations are in progress, you should consider overriding these methods. 

ReadAsync, WriteAsync, FlushAsync

New to .NET 4.5, the Stream class provides three virtual methods for reading and writing asynchronously based on the Task-based Asynchronous Pattern:

public virtual Task<int> ReadAsync(
   
byte[] buffer, int offset, int count, CancellationToken cancellationToken);
public virtual Task WriteAsync(
    byte[] buffer, int offset, int count, CancellationToken cancellationToken);
public virtual Task FlushAsync(CancellationToken cancellationToken);

If you don’t override ReadAsync or WriteAsync, their default implementation in the Stream class will simply use the BeginRead/EndRead or BeginWrite/EndWrite methods to perform the work, returning a Task<int> or Task to represent those calls, in a manner extremely similar to how Task.Factory.FromAsync creates Tasks from Begin/End pairs. So, if you only override the synchronous methods and none of the asynchronous ones, ReadAsync/WriteAsync will return a Task that represents Read/Write running on the ThreadPool, via the default implementation of BeginRead/EndRead/BeginWrite/EndWrite. If you have overridden BeginRead/EndRead/BeginWrite/EndWrite, then ReadAsync/WriteAsync will pick up that custom implementation and use it for its asynchrony. You can of course override ReadAsync/WriteAsync as well, regardless of whether you’ve overridden the Begin/End methods; this is in particular a good idea if you are able to support cancellation throughout the processing of the asynchronous operation, since the XxAsync methods accept a CancellationToken.

In some cases, you may have found it too difficult or error prone to implement the Begin/End methods, even if an asynchronous implementation would have been possible.  In such cases, you might consider overriding ReadAsync/WriteAsync without overriding the Begin/End methods. This will typically be made significantly easier via the C# and Visual Basic support for writing async methods using await.  Then, once you have your Task-based implementations, you might even consider overriding the Begin/End methods and implementing them in terms of the Task-based methods.

The FlushAsync method is the asynchronous counterpart to the synchronous Flush method. The base implementation of Stream.FlushAsync queues to the ThreadPool a call to Flush. If your Stream has a nop Flush method, it’s a good idea to override FlushAsync to simply return an already completed task, as there’s no need to spend energy queueing a work item that itself will be a nop.  Of course, if you can implement FlushAsync using async I/O rather than burning a ThreadPool thread while invoking the synchronous variant, that’s a good thing to consider doing.

CopyToAsync

.NET 4.5 also includes a virtual, asynchronous counterpart to the synchronous (non-virtual) CopyTo method that was introduced in .NET 4:

public virtual Task CopyToAsync(
    Stream destination, int bufferSize, CancellationToken cancellationToken);

The base Stream.CopyToAsync method uses the ReadAsync method on the source stream and the WriteAsync method on the destination stream, calling them in a loop to transfer data from one stream to the other.  However a derived type could choose to override CopyToAsync to provide a more specialized implementation.  For example, MemoryStream already has all of its data buffered in memory, so it can achieve the copy with a single WriteAsync of the entire buffer to the target stream, or even a straight buffer copy if the target is also a MemoryStream.

Leave a Comment
  • Please add 5 and 8 and type the answer here:
  • Post
  • Helpful summary, thanks!

  • can you explain why MemoryStream implements ReadSync/WriteAsync synchronously, but doesn't override the APM methods deferring to the default asynchronous implementations?

    it means that in order to use MemoryStream asynchronously as an abstract stream, you have to wrap it (such as the curiously private System.Net.SyncMemoryStream).

    was IAsyncResult.CompletedSynchronously deprecated at some point?

  • @spongman:

    The reason that MemoryStream implements ReadAsync and WriteAsync synchronously is that for this particular stream type, read and write operations are essentially memory copies: You provide a Byte[] as a buffer and the contents of that buffer are copied into the memory that backs the MemoryStream (or vice versa for writes). This is typically a very fast operation during which there is no need to wait for a disk / network / etc IO to complete. In this respect MemoryStream is different from other typical streams and there is no need to involve the async infrastructure.

    Note that this is just a performance optimisation and does not affect correctness. Since the recommended way to perform async IO is the Task Async Pattern (XxxAsync methods) and not the old APM pattern (BeginXx/EndXx methods), we chose not to implement this optimisation for the BeginRead/EndRead (..Write) methods on MemoryStream.

    I am not fully sure what you mean by your other questions.. How did you want to use MemoryStream as an abstract stream and what was your objective? IAsyncResult.CompletedSynchronously is not deprecated. What caused that impression?

  • Stephen,

    It's all very well introducing these ReadyAsync etc methods, but they are very misleading as many of the underlying implementations don't respect the cancellation token as one would expect. For instance, ReadAsync on TcpClient will block indefinitely if there is no data to be read from the underlying stream. There seems to be no way to cancel this read - either via the cancellation token or directly. This means that regardless of what semantics you use (CancellationTokenSource, Wait(timeout) etc), you just can't cancel the tasks.

    Please can you provide guidance as to what we should be doing - or ideally, when these other (rather fundamental) implementations are going to get a facelift and begin working in a truly asynchronous manner?

    thanks, Andrew

  • Andrew, I hear your concerns, but there's little the base Stream.ReadAsync/WriteAsync/etc. methods can do if the implementation itself doesn't plumb through support for cancellation.  I can't speak to when such types will override these methods to provide more aggressive cancellation support.

  • i guess no progress on the cancellationToken support in the bcl streams (NetworkStream in particular)?

Page 1 of 1 (6 items)