다음을 통해 공유


Writing proper async code

Alsalam alikom wa ra7mat Allah wa barakatoh (aka peace upon you)

Asynchronous calls have always been there for more efficient use of CPU time. You run something asynchronously when you know it’ll take some time to execute and you don’t want to block your current thread while waiting for that code (In some cases you actually do want to block the thread but then spanning another thread is, in this case, more or less a waste of resources In My Humble Opinion).

For the purpose of this post, let’s take as an example downloading a remote file, and printing the contents of the file to screen.

I would imagine this as one way to implement it:

 private void TestSync()
{
    Console.WriteLine("Starting Test() " + GetCurrentContext());
    WriteLinePageTitle(DownloadStringSlowNetworkSync(new Uri("https://sample.com")));
    Console.WriteLine("Test() is done with big delay " + GetCurrentContext());
}

private string DownloadStringSlowNetworkSync(Uri uri)
{
    Thread.Sleep(500);
    return new WebClient().DownloadString(uri);
}

This is an example for a synchronous operation. TestSync() will not return till the string is downloaded. Here is a run:

image

 As you can see, it all ran on the same thread.
 If a UI component is calling TestSync() this means execution will block UI. And that’s the typical use of of Asynchronous method.
 Here is the typical way to implement async:
 private void TestAsync1()
{
    Console.WriteLine("Starting Test() " + GetCurrentContext());
    BeginDownloadStringSlowNetworkAsync(
        new Uri("https://sample.com"),
        (sender, e) =>
        {
            WriteLinePageTitle(e.Result);
        },
        (ex) =>
        {
            WriteLinePageTitle("Exception is thrown: " + ex.Message);
        });
    Console.WriteLine("Test() is done with no delay " + GetCurrentContext());
}

private void BeginDownloadStringSlowNetworkAsync(
    Uri uri, 
    DownloadStringCompletedEventHandler completedCallback, 
    Action<Exception> errorCallback)
{
    Thread.Sleep(500);
    try
    {
        var client = new WebClient();
        client.DownloadStringCompleted += completedCallback;
        client.DownloadStringAsync(uri);
    }
    catch (Exception ex)
    {
        errorCallback(ex);
    }
}

image

 This is a little bit better, now UI is not blocked (notice UI running in thread 1 continued working fine)… 
There are 2 down sides of this implementation,
1- Code isn’t intuitive, it’s way different than the counter sync code.
2- Exception handling is not propagating well enough, stack trace isn’t preserved for instance.
 Now with the better version, using Async Library.
 public async Task TestAsync2()
{
    Console.WriteLine("Starting Test() " + GetCurrentContext());
    RunAsync();
    Console.WriteLine("Test() is done with no delay " + GetCurrentContext());
}

private async void RunAsync()
{
    WriteLinePageTitle(await DownloadStringTaskSlowNetworkAsync(new Uri("https://sample.com")));
}
 public async Task<string> DownloadStringTaskSlowNetworkAsync(Uri address)
{
    await TaskEx.Delay(500);    // Simulate 500ms of network delay
    return await new WebClient().DownloadStringTaskAsync(address);
}

image

You will notice a couple of things here:

1- The result is identical to the async code we wrote above

2- Code looks intuitive, very similar to sync version

3- There are two new keywords async and await. And that’s what I actually want to talk about..

async: you decorate your methods with async to be able to call await inside them.

await: you prefix method calls with await when you want the library to create callbacks for you to handle async operation.

In DownloadStringTaskSlowNetworkAsync() method, we call DownloadStringTaskAsync, which is an extension method included in Async library. What await does is that it creates a callback method that includes the rest of your code (return statement in this case), then calls the method DownloadStringTaskAsync() and registers that callback, when the callback is invoked (ie when the string is actually downloaded), it invokes the rest of your code (return… etc).

Going one level up, RunAsync(), it awaits on DownloadStringTaskSlowNetworkAsync, which means WriteLinePageTitle will not be called till DownloadStringTaskSlowNetworkAsync returns… till then, RunAsync will just return a void task.

One more level up, here we called RunAsync without prefixing the call with await, this means, the function will behave exactly like our previous Begin method, it’ll start, and control flow will directly continue to print “Test() is done…”.

A few things to note:

1- The leaves of the calls (DownloadStringTaskAsync… etc) are implemented by Async Library, if you want to implement your own leaf method, it’s a little bit more interesting, I’ll save that for a coming post.

2- await doesn’t block current thread, it rather wraps up the rest of code in your method and creates a callback to execute the code after async operation is done.

3- If you surrounded an await statement with try,catch, catch statements will be included in that callback, which means you will be able to catch exceptions for async methods the same way you catch exceptions for sync methods

Last to note, Async Library is available for WP7 as well as WPF/Silverlight… I’ve to tell you, it makes async code WAY easier to write!

Stay Tuned Smile

Comments

  • Anonymous
    July 09, 2011
    NICE ! .. thanks for sharing