DevTalk.net

ActiveMesa's software development blog. MathSharp, R2P, X2C and more!

Async, Await and C# vNext

without comments

It would be somewhat naïve not to discuss the new stuff we’ve been told about at the PDC, so I figure it’s a good time to discuss the Async CTP, specifically, but also it’s another opportunity to speculate on the subject of C# vNext.

What Was

Before we got continuations in the Async CTP (download it now if you haven’t – it’s a non-breaking incremental update), barring doing our own BeginXxx/EndXxx-related management we had two legitimate and somewhat understandable ways of doing continuations.

The first option was to use F# async workflows. The idea itself is rather primitive – an F# workflow is simply a named scope within which the behavior of certain operators – such as let! or return! is redefined to do something related specifically to that workflow. In the case of F#, we had a mechanism for defining our own Begin and End pairs…

type WebRequest with
  member x.GetResponseAsync() =
    Async.BuildPrimitive(x.BeginGetResponse, x.EndGetResponse)

…and subsequently within an async workflow we could use the let! and return! operators to wait for an operation to complete.

let private DownloadPage(url:string) =
  async {
    try
      let r = WebRequest.Create(url)
      let! resp = r.GetResponseAsync() // wait for the result
      use stream = resp.GetResponseStream()
      use reader = new StreamReader(stream)
      let html = reader.ReadToEnd()
      use fs = new FileStream(@"c:\temp\file.htm", FileMode.Create,
                              FileAccess.Write, FileShare.None, 1024, true);
      let bytes = Encoding.UTF8.GetBytes(html);
      do! fs.AsyncWrite(bytes, 0, bytes.Length) // wait while everything is written
    with
      | :? WebException -> ()
  }
 
Async.RunSynchronously(DownloadPage("http://devtalk.net"))

The second option for managing callbacks was given to us by Jeffrey Richet’s AsyncEnumerator (part of the PowerThreading library). The trick here is to use iterators and the yield keyword to ‘step out’ of a method, thereby instrumenting an artificial continuation. This option, while innovative, does have its shortcomings, such as an inability to work within try-catch blocks.

public IEnumerator<int> DownloadPage(string url, AsyncEnumerator ae)
{
  var wr = WebRequest.Create(url);
  wr.BeginGetResponse(ae.End(), null);
  yield return 1;
  var resp = wr.EndGetResponse(ae.DequeueAsyncResult());
  var stream = resp.GetResponseStream();
  var reader = new StreamReader(stream);
  string html = reader.ReadToEnd();
  using (var fs = new FileStream(@"c:\temp\file.htm", FileMode.Create,
                      FileAccess.Write, FileShare.None, 1024, true))
  {
    var bytes = Encoding.UTF8.GetBytes(html);
    fs.BeginWrite(bytes, 0, bytes.Length, ae.End(), null);
    yield return 1;
    fs.EndWrite(ae.DequeueAsyncResult());
  }
}

In fact, from what we heard on the PDC (and beforehand) it was Jeffrey’s consulting that helped Microsoft come up with what we now got as Async CTP.

What Is

So, we now have support for continuations in C# and VB, though the mechanisms are different. In actual fact, everything is painfully easy.

First of all, we got an ability to “await” something, and that’s what the new await keyword is for. As a parameter, it takes a Task, and simply waits for the task (beneath it is a TaskAwaiter – just generate a dependency diagram to explore this stuff). The real upside here is that through the Task-related infrastructure we can manage cancelations and exceptions better. And, of course, we’re already used to tasks since they’re part of TPL.

Second, we can now do async calls not via Begin-End pairs, but by simply marking the method as async and awaiting it. This saves us from IAsyncResult-related headaches. Now, all that is required to make an asynchronous method is to use the async keyword and change its return type to Task (more on that later).

By the way, the async and await keywords are intertwined – if a method uses await, then it must be marked as async.

Okay, so let’s look at a ‘live’ example with the same old scenario – downloading a webpage and saving it to a local file.

public static async Task DownloadInformation(int id)
{
  Contract.Requires(id >= 0);
  
  string filename = @"c:\temp\somesite.com\" + id + ".htm";
  if (File.Exists(filename))
    return;
  var ct = new CancellationToken();
  var r = new Reporter(id);
  string data = await new WebClient().DownloadStringTaskAsync(
    new Uri(@"http://somesite.com/" + id), ct, r);
  
  using (var fs = new FileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.Write))
  {
    var bytes = Encoding.Default.GetBytes(data);
    await fs.WriteAsync(bytes, 0, bytes.Length);
    fs.Close();
  }
}

The first paradox of perception is that even though our method doesn’t return anything (can you see a return anywhere?), its return type is not void but rather Task. This is the Async CTP trick, though I’m not sure how adequate it is (c’mon guys, you’re breaking the natural match between a method’s return type that that which is actually returned). At any rate, the idea is that the method which uses await must be marked as async and must also have a return type of Task (if it is void) and, predictably enough, Task<T> if the method returns a T.

Unfortunately, out/ref parameters are not supported, which will cause a small outcry because, as you all know, working with tuples in C# is not exactly easy.

So… in addition to the typical methods, the Async CTP adds a DownloadStringTaskAsync() method to the WebClient, which is a pretty good hint as to what will happen there (though it can be confusing because WebClient already has a DownloadStringAsync() method). In addition, for example, the FileStream class gets a WriteAsync() method (where is the Task bit?), though my opinion is that it would be a lot more convenient if they added (hey, I’m sure they will) a File.WriteAllTextAsync() so I don’t have to bother with all the extra initialization. At any rate, both methods are usable with await.

Oh, speaking of which, one useful thing which I liked is the ability to use an IProgress<> implementation as a parameter to DownloadStringTaskAsync(). We often need to track the completion of a file download, and having this built-in is just great. I’m using it in the code above, with the definition of the Reporter class as follows:

private class Reporter : IProgress<DownloadProgressChangedEventArgs>
{
  private int id;
  public Reporter(int id)
  {
    this.id = id;
  }
  public void Report(DownloadProgressChangedEventArgs value)
  {
    Console.WriteLine("{0}:{1}", id, 
      string.Empty.PadRight(value.ProgressPercentage / 10, '*'));
  }
}

Now, here’s my way of invoking the above code:

static async void Main()
{
  var ct = new CancellationTokenSource();
  Console.CancelKeyPress += (s, e) => ct.Cancel();
  try
  {
    var po = new ParallelOptions();
    po.MaxDegreeOfParallelism = 64;
    po.CancellationToken = ct.Token;
    Parallel.For(1, 100, po, i =>
    {
      po.CancellationToken.ThrowIfCancellationRequested();
      DownloadInformation(i);
    });
  } 
  catch (Exception e)
  {
    Console.WriteLine(e);
  }
  finally
  {
    Console.ReadKey();
  }
}

What’s of note here is that we also had to mark Main() as async – there is a viral nature to async calls!

And now for the things I didn’t like. First, I’m as yet uncertain about what to do if the URL of the file to be downloaded doesn’t resolve to anything (i.e., page doesn’t exist). Right now, everything just stops and I get no exception or other indication of what’s going on. Another thing that worries me is that Debugging doesn’t work for me yet – right after the first await it bounces me to the caller, and that’s it. Then again, this is a CTP, so I guess I have to be patient.

Now What?

So now we got ourselves the ability to ‘lighten up’ on syntax, and code readbility has grown even compared to F#. We can now describe algorithms with continuations though, predictably enough, it makes little sense for singular calls – it’s mainly benefitial if you are making lots of these, either via Parallel.For() or, for example, by creating several tasks and then doing Task.WaitAll().

Since the Async CTP came out, the C# mailing lists have exploded – everyone’s experimenting with the ‘kit’ and trying things out. I actually put the code above into ‘live’ use – the risk is minimal and everything is working well, so I can afford it. The code above has already worked for a few hours, and I have no complaints so far. Then again, this use-case is fairly trivial.

Other PDC Goodies

It would be nice if Anders mentioned something mind-blowing, but instead I was somewhat surprised even that while talking about metaprogramming in C#, when asked about aspect-oriented programming, he said he wasn’t sure it’s a good idea and that C# vNext is unlikely to support it. I find this bizarre because my understanding of metaprogramming is having a capability to do exactly that. Of course, the differentiator here is that, e.g., PostSharp’s approach is to post-process existing assemblies, which might not be what the ‘compiler-as-a-service’ is attuned for.

Then again, I don’t need metaprogramming for AOP – I need it to auto-implement INotifyPropertyChanged, generatively create complex data structures or help create internal DSLs for better descriptions of algorithms. And C# vNext should be perfect for that. Unless of course I get the temptation to migrate everything to JetBrains MPS. Hey, it could happen. ■

Written by Dmitri Nesteruk

October 31st, 2010 at 10:40 am

Posted in CSharp