Mike Taulty's Blog
Bits and Bytes from Microsoft UK
AsyncOperationManager.

Blogs

Mike Taulty's Blog

Elsewhere

Archives

I learnt something new today that I hadn't seen before in .NET 2.0. I got it from this link here and it is somewhat related to something I posted a long time ago about capturing ExecutionContext back here
 
Essentially, it was this class called AsyncOperationManager which, as far as I can see, helps when you're trying to write a component which might call back (or fire events) into a consumer and that consumer might have a particular context that they're bound to (e.g. the consumer might be a windows forms control which has thread affinity and needs to be called on the thread that it was created on).
 
To play around with this I wrote this little class (note: it's not meant to be bullet proof! it's just an example) that asynchronously reads a file from the disk and notifies a consumer as progress is made in reading that file.
 
  public class AsyncFileReadProgressEventArgs : EventArgs
  {
    public AsyncFileReadProgressEventArgs(int percentageRead)
    {
      this.percentageRead = percentageRead;
    }
    private int percentageRead;
 
    public int PercentageRead
    {
      get { return percentageRead; }
      set { percentageRead = value; }
    }
  }
 
  class AsyncFileReader
  {
    public AsyncFileReader(string fileName)
    {
      this.fileName = fileName;
    }
    public void ReadAsynchronously()
    {
      buffer = new byte[1024];
 
      fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
 
      size = fileStream.Length;
      bytesRead = 0;
 
      fileStream.BeginRead(buffer, 0, buffer.Length, OnReadCompleted, fileStream);
    }
    private void OnReadCompleted(IAsyncResult result)
    {
      FileStream fs = (FileStream)result.AsyncState;
 
      int read = fs.EndRead(result);
 
      bytesRead += read;
 
      if (read == 0)
      {
        if (ReadCompleted != null)
        {
         ReadCompleted(this, null);
        }
        fs.Close();
      }
      else if (ReadProgressed != null)
      {
        AsyncFileReadProgressEventArgs args = new AsyncFileReadProgressEventArgs(
          (int)(((double)bytesRead / (double)size) * 100.0));
 
        ReadProgressed(this, args);
      }
 
      if (read != 0)
      {
        fs.BeginRead(buffer, 0, buffer.Length, OnReadCompleted, fs);
      }
    }
    public event EventHandler<AsyncFileReadProgressEventArgs> ReadProgressed;
    public event EventHandler ReadCompleted;
 
    private string fileName;
    private FileStream fileStream;
    private byte[] buffer;
    private long size;
    private long bytesRead;
  }
 
So, it's not a particularly useful class and it undoubtedly has some bugs in it.
 
Now, if the consumer of this class is a Windows Forms application and it handles the ReadProgressed and ReadCompleted events then it'll have to do Control.Invoke not to avoid thread-safety issues.
 
However, if we modify this to use the AsyncOperationManager then there's no need to do that.
 
class AsyncFileReader
  {
    public AsyncFileReader(string fileName)
    {
      this.fileName = fileName;
    }
    public void ReadAsynchronously()
    {
      buffer = new byte[1024];
 
      operation = AsyncOperationManager.CreateOperation(null);
 
      fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
 
      size = fileStream.Length;
      bytesRead = 0;
 
      fileStream.BeginRead(buffer, 0, buffer.Length, OnReadCompleted, fileStream);
    }
    private void OnReadCompleted(IAsyncResult result)
    {
      FileStream fs = (FileStream)result.AsyncState;
 
      int read = fs.EndRead(result);
 
      bytesRead += read;
 
      if (read == 0)
      {
        if (ReadCompleted != null)
        {
          operation.Post(delegate {
            ReadCompleted(this, null);
          }, null);
         
          operation.OperationCompleted();
        }
        fs.Close();
      }
      else if (ReadProgressed != null)
      {
        AsyncFileReadProgressEventArgs args = new AsyncFileReadProgressEventArgs(
          (int)(((double)bytesRead / (double)size) * 100.0));
 
        operation.Post(delegate {
          ReadProgressed(this, args);
        }, null);      
      }
 
      if (read != 0)
      {
        fs.BeginRead(buffer, 0, buffer.Length, OnReadCompleted, fs);
      }
    }
    public event EventHandler<AsyncFileReadProgressEventArgs> ReadProgressed;
    public event EventHandler ReadCompleted;
 
    private AsyncOperation operation;
    private string fileName;
    private FileStream fileStream;
    private byte[] buffer;
    private long size;
    private long bytesRead;
  }
 
So, as far as I understand it when we do the AsyncOperationManager.CreateOperation we're capturing the client context and later on when we do a Post() the delegate that we pass (in our case, an anonymous method that fires an event) will be called back in the client's original context.
 
Neat! All errors mine as I've only just found this class.

Posted Wed, Nov 30 2005 6:31 AM by mtaulty