The Progress Reporting Pattern in C# 5 async

How to use it and how it works.
By Nicholas Butler

profile for Nicholas Butler at Stack Overflow, Q&A for professional and enthusiast programmers
Publicly recommend on Google


Downloads

Table of Contents

Introduction

This article is about the new TAP - the Task-based Asynchronous Pattern - and concentrates on the support for progress reporting.

I explain a simple program that gets text from a web server asynchronously. As I show each layer of the solution, I explain the implementation and framework support. I will assume you already know how async and await work.

Firstly, I will show you how simple this has become for application developers.
Then I will go under the hood and explore the implementation of a TAP method with progress reporting.

The Requirement

I have hosted a page on my web server that shows a short list of trite quotes, generated randomly from a list I obtained... The page takes 7 seconds to generate a list of 8 quotes. Each quote is flushed to the network as it is generated, one per second.

The requirement is to write an application that consumes this service and shows both the current progress percentage and all the quotes as soon as they arrive from the ether.

The Client

I wrote a WinForms app as the client. Here is the entire code for the main Form:

public partial class Form1 : Form
{
   public Form1()
   {
      InitializeComponent();

      Shown += async ( s, e ) => { txtResult.Text = await DownloadAsync() + "Done!"; };
   }

   async Task<string> DownloadAsync()
   {
      using ( var wc = new WebClient() )
      {
         var progress = new Progress<DownloadStringTaskAsyncExProgress>();

         progress.ProgressChanged += ( s, e ) =>
            {
               progressBar.Value = e.ProgressPercentage;
               txtResult.Text += e.Text;
            };

         return await wc.DownloadStringTaskAsyncEx(
            @"http://ancillaryasync.nickbutler.net/Murphy.ashx", progress );
      }
   }
}

public class DownloadStringTaskAsyncExProgress
{
   public int ProgressPercentage { get; set; }
   public string Text { get; set; }
}

There is quite a lot going on here. I will explain each piece in turn.

Shown event handler

Shown += async ( s, e ) => { txtResult.Text = await DownloadAsync() + "Done!"; };

I am using a lambda to handle this event. Notice that you can use async and await here too, not just for named methods.

The method that does the work, DownloadAsync(), eventually returns a string. When this method completes, the handler just appends "Done!" to the result and shows that. This is how I know the whole process has finished.

Doing the work

return await wc.DownloadStringTaskAsyncEx( @"http://ancillaryasync.nickbutler.net/Murphy.ashx", progress );

The work is done by an extension method on the WebClient class: DownloadStringTaskAsyncEx. This is also a TAP method, so I can use await to yield control while it is executing.

It takes a URI and returns a string - all well and good. But it also takes an object called progress as a parameter. This is the new pattern for progress reporting.

The progress event

I'll fudge a little bit here and gloss over the implementation of the progress object. It uses some new classes in .NET 4.5 and deserves a section to itself.

Just assume I have some progress object:

var progress = new Progress<DownloadStringTaskAsyncExProgress>();

All I need to tell you now is that it has a ProgressChanged event that is raised by the TAP method at suitable points during its execution. This event will be raised on the UI thread, and the e parameter will be an instance of my DownloadStringTaskAsyncExProgress class that contains info about the current progress. So, all I need to do is add a handler that updates my UI controls:

progress.ProgressChanged += ( s, e ) =>
{
   progressBar.Value = e.ProgressPercentage;
   txtResult.Text += e.Text;
};

Well, now we have our client code, so now for the fun bit...

The TAP method

There is a new TAP method on WebClient, but it doesn't take an IProgress<T> parameter and doesn't give access to the response stream, so it doesn't meet the requirements.

public Task<string> DownloadStringTaskAsync( string address ) { ... }

However, there is another new TAP method on WebClient that returns a Stream:

public Task<Stream> OpenReadTaskAsync( string address ) { ... }

And the Stream class also has some new TAP methods. Here is the one I need:

public Task<int> ReadAsync( byte[] buffer, int offset, int count );

Implementation

I used these two methods to write the implementation of DownloadStringTaskAsyncEx, with progress reporting that includes the result text as it arrives.

Here is the entire source:

public class DownloadStringTaskAsyncExProgress
{
   public int ProgressPercentage { get; set; }
   public string Text { get; set; }
}

static class WebClientExtensions
{
   public static async Task<string> DownloadStringTaskAsyncEx(
      this WebClient wc,
      string url,
      IProgress<DownloadStringTaskAsyncExProgress> progress )
   {
      var receivedBytes = 0;
      var receivedText = String.Empty;

      using ( var stream = await wc.OpenReadTaskAsync( url ) )
      {
         int totalBytes = -1;
         Int32.TryParse( wc.ResponseHeaders[ HttpResponseHeader.ContentLength ], out totalBytes );

         var buffer = new byte[ 4096 ];

         for ( ; ; )
         {
            int len = await stream.ReadAsync( buffer, 0, buffer.Length );
            if ( len == 0 ) { await Task.Yield(); break; }

            string text = wc.Encoding.GetString( buffer, 0, len );

            receivedBytes += len;
            receivedText += text;

            if ( progress != null )
            {
               var args = new DownloadStringTaskAsyncExProgress();
               args.ProgressPercentage = ( totalBytes < 0 ? 0 : ( 100 * receivedBytes ) / totalBytes );
               args.Text = text;
               
               progress.Report( args ); // calls SynchronizationContext.Post
            }
         }
      }

      return receivedText;
   }
}

This TAP method also happens to be an async method. This is perfectly correct and allowed me to use the TAP forms of WebClient.OpenRead and Stream.Read. This means that there is no blocking in the method and so it is safe to execute on the UI thread.

One interesting detail is that I must create a new instance of DownloadStringTaskAsyncExProgress each time I call progress.Report(). This is because the ProgressChanged event is marshalled on to the UI thread by using SynchronizationContext.Post(). If I tried to reuse a single Progress object, there would be a race condition between the event handlers and the next call to Report().

That's all there is to it: the caller creates an IProgress<T> object and passes it in and all the TAP method has to do is call Report().

The Progress object

So what is this magic progress object? There are a few new types we need to look at here.

IProgress<T>

The signature for DownloadStringTaskAsyncEx actually takes an IProgress<T>. This is a new interface in .NET 4.5, defined in the mscorlib.dll assembly.

namespace System
{
  // Summary:
  //     Defines a provider for progress updates.
  //
  // Type parameters:
  //   T:
  //     The type of progress update value.This type parameter is contravariant. That
  //     is, you can use either the type you specified or any type that is less derived.
  //     For more information about covariance and contravariance, see Covariance
  //     and Contravariance in Generics.
  public interface IProgress<in T>
  {
    // Summary:
    //     Reports a progress update.
    //
    // Parameters:
    //   value:
    //     The value of the updated progress.
    void Report( T value );
  }
}

It only has one method: void Report( T value ). Remember, an object that implements this interface is passed into the TAP method. The implementation can call the Report method of this object when it wants to report progress.

Progress<T>

Now I need to create the progress object, so I need a class that implements the interface.

Fortunately, there is an implementation of IProgress<T> in the framework, called just Progress<T>. It is also in mscorlib.dll and here it is:

namespace System
{
  // Summary:
  //     Provides an System.IProgress<T> that invokes callbacks for each reported
  //     progress value.
  //
  // Type parameters:
  //   T:
  //     Specifies the type of the progress report value.
  public class Progress<T> : IProgress<T>
  {
    // Summary:
    //     Initializes the System.Progress<T> object.
    public Progress();
    
    //
    // Summary:
    //     Initializes the System.Progress<T> object with the specified callback.
    //
    // Parameters:
    //   handler:
    //     A handler to invoke for each reported progress value. This handler will be
    //     invoked in addition to any delegates registered with the System.Progress<T>.ProgressChanged
    //     event. Depending on the System.Threading.SynchronizationContext instance
    //     captured by the System.Progress<T> at construction, it is possible that this
    //     handler instance could be invoked concurrently with itself.
    public Progress( Action<T> handler );

    // Summary:
    //     Raised for each reported progress value.
    public event EventHandler ProgressChanged;

    // Summary:
    //     Reports a progress change.
    //
    // Parameters:
    //   value:
    //     The value of the updated progress.
    protected virtual void OnReport( T value );
  }
}

Basically, when you instantiate this class, it captures the current thread's SynchronizationContext. Then, each time Report is called from inside the TAP method, it raises the ProgressChanged event on the right thread.

Note that Report() uses a SynchronizationContext.Post(). This is why you would get a race condition between previous events being handled and subsequent calls to Report, if you reused your value objects ( instances of T ) in your TAP method.

Conclusion

As I have shown, the new Task-based Asynchronous Pattern makes this sort of code much easier to write - and read. Consumers of TAP methods are almost trivial. Even if you have to write a method TAP yourself, it is still quite simple if you know the patterns.

The async and await keywords just work. Anders and the team have done a great job. The arduous bit for Microsoft was implementing TAP versions of the methods in the framework that block on IO. Application developers can now give their users more information about what an app is doing in an increasingly asynchronous world.

Well, that's it. I hope you have enjoyed reading this article. Thanks.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License.

Contact

If you have any feedback, please feel free to contact me.

Publicly recommend on Google: