Thursday 10 March 2016

From Callback to Await

Let's say that you have a "traditional" asynchronous method that will invoke a callback once it's finished, something like this:

public void Read(string path, Action<string> callback)
  {
   //let's simulate the async read operation
   //this does not block, it will just run the callback in a Threadpool thread
   new Timer((state) => callback("this is the file content"), null, 4000, Timeout.Infinite);
  }

What kind of wrapper around it could we create to use it with the cool await keyword? Without taking into account additional classes provided by the framework, and just using something I were familiar with, I came up with creating a new thread (either directly or through an additional Task, launching the call to the asynchronous method from it and waiting for a signal. This signal will be set from the callback. So we have something like this:

public Task<String> Read2(string path)
  {
   var waitHandle = new ManualResetEvent(false);
   string result = null;
   Action<string> callback = (res) => {
result = res;    
waitHandle.Set();
    
   };
   return Task.Run(() => {
                    //Task.Run will run it in the ThreadPool, so it's better
                    //new Thread(() => this.Read(path, callback)).Start();
                    Task.Run(() => this.Read(path, callback));
                    waitHandle.WaitOne();
        return result;
   });
  }

Nice, but I guessed there had to be a cleaner way to do it. Do a search and you'll find that the TaskCompletionSource class was created just for that. You can rewrite the above like this:


  public Task<String> Read3(string path)
  {
       var t = new TaskCompletionSource<string>();
  
       this.Read(path, s => t.TrySetResult(s));
  
       return t.Task;
  }

Reading the MSDN information about the class brings up an interesting point:

Represents the producer side of a Task unbound to a delegate, providing access to the consumer side through the Task property.

I had not thought about it this way previously. We've heard of this Producer/Consumer couple in other scenarios, like Enumerators, Events... For a Task, it's clear that the consumer is the client that waits on the Task and read a result from it. The Producer (of that Result) usually is the Task itself (the code running in that task), but with the TaskCompletionSource the Task has not code assigned, it's a different "piece" what runs and will eventually produce a result that will be propagated to the Consumers through the Task.

No comments:

Post a Comment