The Async Pattern used in .NET 1.x and 2.0 code always annoyed me because of the excessive coding noise it introduced. The new support in .NET 3.x in relation to lamdas (convenient delegate or callback syntax) and the generic representation of Actions, Functions and Predicates opens up some possibilities for writing asynchronous oriented code without all that cruft. Check out the IStreamReader class below. It defines a series of events related to the stream reading functionality. Each of these is defined as an event that calls the subscriber back via an Action definition. Each Action type is parameterized based on the needs of the event callback functionality. The beauty of this is that the application code is subsequently much more readable as can be seen by the automated test segment at the end of this blog post.

/// <summary>
/// An object that asynchronously reads bytes from a data stream.
/// </summary>
public interface IStreamReader
{
    /// <summary>
    /// Occurs when a block of bytes has been read by this reader. The data
    /// bytes buffer that were just read and the number of bytes placed in
    /// that buffer are supplied to the event callbacks.
    /// </summary>
    event Action<byte[], int> BytesRead;

    /// <summary>
    /// Occurs when progress is made by this reader. The event call back
    /// provides the number of bytes read to date and the number of bytes
    /// expected to be read in total. If the total number of bytes in the
    /// stream is unknown then the total count supplied is -1.
    /// </summary>
    event Action<int, int> ProgressMade;

    /// <summary>
    /// Occurs when an error stops the read process from proceeding.
    /// </summary>
    event Action<Exception> ErrorOccurred;

    /// <summary>
    /// Occurs when the read action is complete. This event provides no
    /// additional information so is only practically usable when the
    /// <see cref="BytesRead"/> event was subscribed to.
    /// </summary>
    event Action ReadCompleted;

    /// <summary>
    /// Occurs when all data bytes have been read from this stream. The
    /// entire data block that was read is supplied to the event callback
    /// along with the number of bytes that was read. This event should only
    /// be used where the files read can practically fit in memory. If this
    /// is not the case, then a combination of the <see cref="BytesRead"/>
    /// and <see cref="ReadCompleted"/> events can be used instead to process
    /// the large file in manageable chunks.
    /// </summary>
    event Action<byte[], int> EntireFileRead;

    /// <summary>
    /// Initiates a read of the entire stream.
    /// </summary>
    void ReadAll();

    /// <summary>
    /// A convenience method for when this asynchronous oriented class is used
    /// in a synchronous manner. This method waits for the read actions to
    /// complete or fail before proceeding.
    /// </summary>
    void WaitForCompletion();
}
///

In the following test code segment, an error is not expected to occur so rather than handling the error callback we throw the exception object supplied to fail the test. If the entire file is read successfully a lamda is used to set the text that has been read. In typical applications, the WaitForCompletion method would not be used but it useful in test code and single action applications to wait for the read functionality to complete.

[Test]
public void Disk_Stream_Reads_Entire_File_Successfully()
{
    string text = null;
    IStreamReader reader = GetTestReader("MartinLutherSpeech.txt");
    
    reader.ErrorOccurred += ex => { throw ex; };
    reader.EntireFileRead += (data, length) => text = Encoding.ASCII.GetString(data, 0, length);
    
    reader.ReadAll();
    reader.WaitForCompletion();
    
    Assert.IsNotNull(text);
    Assert.AreEqual(9190, text.Length);
    Assert.IsTrue(text.StartsWith("I am happy to join"));
    Assert.IsTrue(text.EndsWith("we are free at last!\""));
}

The new functional aspects of the .NET 3.x functionality really changes the way we develop with the .NET framework. Practically speaking its going to take some time to get use to the new ways of thinking and applying them to this multi-core world we find ourselves in.