Share via


TPL Dataflow and async/await vs CCR - part 2

Dealing with asynchronous APIs will also be much easier than with CCR. First of all you can expect most (if not all) classes in the .Net framework to have another method added to them that is declared async. For example the Stream object used to have a synchronous Read method and then BeginRead and EndRead for asynchronous processing. In .Net 4.5 the Stream object also have ReadAsync which can be used to process asynchronously. If you however need to work with an API that have not been updated with an async method you can either apply the same pattern as with CCR by using TPL data-flow or you can just spawn and await two tasks like this:

  1: var asyncResult = default(IAsyncResult);
 2: var result = default(int);
 3: var mre = new ManualResetEvent(false);
 4: await Task.Run(() => 
 5:     {
 6:         stream.BeginRead(buffer, offset, count, 
 7:             r => { asyncResult = r; mre.Set(); }, null);
 8:         mre.WaitOne();
 9:     });
 10: await Task.Run(() => result = stream.EndRead(asyncResult));

You could naturally wrap this in a helper method to deal with this generically exactly the same way as with CCR.

UPDATE: First of all I was too quick to post this so there was an error in the original code. The use of BeginRead must wait on completion before completing. Second (I guess that is the price for using the latest stuff) I missed to point out that there already is a helper for making Begin/End into a task; Task.Factory.FromAsync. Last (but not least) I admit the code above is kind of stupid and yes it would block a thread pool thread. I was stuck in a pattern commonly used in CCR. Stephen Toub's first comment below show's how this should really be done using a TaskCompletionSource object. So to really learn something useful, look at that comment and the use of TaskCompletionSource.

Comments

  • Anonymous
    December 17, 2011
    Nice! > You could naturally wrap this in a helper method... There's also Task.Factory.FromAsync(...) One overload of which takes a Begin/End pair

  • Anonymous
    December 18, 2011
    Wouldn't that block a threadpool thread waiting for stream.EndRead? I think Task.Factory.FromAsync should be used for that purpose and it can be wrapped in a ReadAsync extension method on Stream.

  • Anonymous
    December 18, 2011
    Hi Emil- There's actually no need to use Task.Run in this case.  For the call to BeginRead, you'll be queueing work to the ThreadPool just to call the BeginRead.  Then you'll be queueing work to the ThreadPool that will block while waiting for the EndRead call to complete.  Instead, if ReadAsync didn't exist, you could just use the FromAsync method, e.g. int bytesRead = await Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, buffer, offset, count, null); FromAsync is just a helper function included in TPL.  You could manually expand that into code like: public static Task<int> ReadAsync(this Stream s, byte [] buffer, int offset, int count) {    var tcs = new TaskCompletionSource<int>();    s.BeginRead(buffer, offset, count, iar =>    {        try { tcs.TrySetResult(s.EndRead(iar)); }        catch(Exception exc) { tcs.TrySetException(exc); }    }, null);    return tcs.Task; } No threads will be blocked unnecessarily in either of these cases.

  • Anonymous
    December 18, 2011
    I should not have completed this post in the middle of the night. Text has been updated. Sorry about that.