다음을 통해 공유


What’s Next in C#? Get Ready for Async!

Today we announced the Visual Studio Async CTP, which shows one of the major features we plan to include in a future release of C# and Visual Basic. This feature makes development of asynchronous applications--which include everything from desktop applications with responsive UI to sophisticated web applications--much easier.

The future release will introduce two new keywords to the C# language: await and async. The await keyword is used to mark asynchronous calls, so that you don’t need to create callback functions anymore and can write code in the same way as if it were synchronous. The compiler will do all of the heavy lifting for you. The async keyword must be presented in the signature of the method that makes asynchronous calls. Briefly, you can use the await keyword only if the async keyword is in the method signature. The same is true for lambda expressions.

This post presents just a short description of the feature. I strongly recommend that you visit the Visual Studio Async CTP page to learn more of the details and get tons of useful resources, including the language spec and cool samples.

Although we have plenty of examples and resources available, it’s never enough. So I’m giving you one more demonstration of how much easier it might be to create asynchronous programs with a future version of the C# and Visual Basic languages.

First, some preparatory steps to make the example work:

  1. Download the Visual Studio Async CTP and install it.
  2. Open Visual Studio 2010 and create a new Silverlight 4 project (To make things easier later, name it FacebookAsync.)
  3. Add a reference to AsyncCtpLibrary_Silverlight.dll. It should be in My Documents/Microsoft Visual Studio Async CTP/Samples.
  4. Add a reference to the System.Json library, because it’s used in the example. This is a standard Silverlight library.
  5. Go to the properties of the web project and, on the Web page, check what port it uses for the Visual Studio Development Server. My sample application uses the port 50750, so it might be easier for you to use the same one.
  6. Register your application on Facebook. You will get an application ID (which is the same as a client ID) and a secret key.

The FacebookAsync application I want to show you downloads a list of friends from Facebook and displays their names and Facebook IDs. It has a button and a DataGrid control that autogenerates columns. Here is its XAML.

<UserControl x:Class="FacebookAsync.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="445" d:DesignWidth="514" xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot" Background="White">
        <sdk:DataGrid AutoGenerateColumns="True" Height="282" HorizontalAlignment="Left" Margin="20,69,0,0" Name="searchResultsGrid" VerticalAlignment="Top" Width="390" />
        <Button Content="Get list of friends from Facebook" Height="23" HorizontalAlignment="Left" Margin="25,21,0,0" Name="button1" VerticalAlignment="Top" Width="206" Click="button1_Click" />
    </Grid>
</UserControl>

To get a list of friends, an application needs to perform several steps (according to Facebook Graph API), including making two asynchronous calls:

  1. You need to redirect from the application page to the Facebook authorization page and provide a client ID, a secret key, and a return URL as parameters of the URL. The user should authorize the application. (In debugging mode the user is probably you, so you should have your own Facebook account.)
  2. If the user authorizes the application, the Facebook API redirects to a return URL, which your application provides. Facebook also adds an authorization code into its redirect URL as a parameter. No asynchronous calls are made yet, because this is done by simply navigating between pages.
  3. The application should exchange the authorization code to an access token by using the GET request. Remember that Silverlight doesn’t have a synchronous API, so you have to make an asynchronous call.
  4. After the application gets a token, it makes a new GET request to the Facebook API to get a list of friends for the current user. Once again, this call should be asynchronous.
  5. The application parses the response from Facebook and displays it on the page.

The full code of the version that doesn’t use the new async feature might look like this.

public partial class MainPage : UserControl
{
    // Remember to change to your application URL.
    string redirect_uri = @"https://localhost:50750/FacebookAsyncTestPage.aspx";

    // Get these values by authorizing your application on Facebook.
    string client_id = "Your Application ID";
    string client_secret = "Your Secret Key";

    // This collection will contain the list of friends.
    ObservableCollection<FacebookFriend> searchResults =
        new ObservableCollection<FacebookFriend>();

    public MainPage()
    {
        InitializeComponent();
        searchResultsGrid.ItemsSource = searchResults;

        // Check whether it was a redirect from the Facebook authorization page.
        if (HtmlPage.Document.DocumentUri.Query.Contains("?code="))
        {
            GetFriends();
        }
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        // Navigating to a Facebook authorization page.
        // No asynchronous calls are necessary.
        var facebookAuthUri =
            new StringBuilder(@"https://graph.facebook.com/oauth/authorize?");
        facebookAuthUri.Append("client_id=").Append(client_id)
            .Append(@"&redirect_uri=").Append(redirect_uri);
        HtmlPage.Window.Navigate(new Uri(facebookAuthUri.ToString()));
    }

    public void GetFriends()
    {
        var code = HtmlPage.Document.DocumentUri.Query.TrimStart('?');
        var facebookUri =
            new StringBuilder(@"https://graph.facebook.com/oauth/access_token?");
        facebookUri.Append("client_id=").Append(client_id)
            .Append(@"&redirect_uri=").Append(redirect_uri)
            .Append(@"&client_secret=").Append(client_secret)
            .Append('&').Append(code);

        // First asynchronous call, to get an access token.
        WebClient proxy = new WebClient();
        proxy.DownloadStringCompleted +=
            new DownloadStringCompletedEventHandler(OnDownloadCompleted_Token);
        proxy.DownloadStringAsync(new Uri(facebookUri.ToString()));
    }

    // Processing results of the first asynchronous call.
    void OnDownloadCompleted_Token(
        object sender, DownloadStringCompletedEventArgs e)
    {
        // Second asynchronous call, to get a list of friends.
        WebClient proxy = new WebClient();
        proxy.DownloadStringCompleted +=
            new DownloadStringCompletedEventHandler(OnDownloadCompleted_Friends);
        var facebookFriendsUri = new StringBuilder(
     @"https://graph.facebook.com/me/friends?fields=id,name&");
        facebookFriendsUri.Append((String)e.Result);
        proxy.DownloadStringAsync(new Uri(facebookFriendsUri.ToString()));
    }

    // Processing results of the second asynchronous call.
    void OnDownloadCompleted_Friends(
        object sender, DownloadStringCompletedEventArgs e)
    {
        JsonObject jsonObject = (JsonObject)JsonValue.Parse((String)e.Result);
        JsonArray jsonArray = (JsonArray)jsonObject["data"];
        foreach (var friend in jsonArray)
        {
            searchResults.Add(
                new FacebookFriend() {
                    Name = friend["name"], ID = friend["id"] });
        }
    }
}

// Items displayed in the data grid.
public class FacebookFriend
{
    public string Name { get; set; }
    public string ID { get; set; }
}

This is what you typically see when you have to deal with asynchronous applications: lots of callbacks, and overall the code is rather hard to read, although I spent some time on beautifying it. Now, let’s see how much easier it might be with the new async feature.

XAML will not change. The same is true for the FacebookFriend class, the button’s event handler, and all the properties in the MainPage class.

The changes start, unsurprisingly, in the GetFriends method. I’m going to make asynchronous calls by using the await keyword. But to enable this, I have to add the async keyword to the method signature.

public async void GetFriends()

Now I can start changing the code within the method. Let’s take a look at the existing code:

// First asynchronous call, to get an access token.
WebClient proxy = new WebClient();
proxy.DownloadStringCompleted +=
    new DownloadStringCompletedEventHandler(OnDownloadCompleted_Token);
proxy.DownloadStringAsync(new Uri(facebookUri.ToString()));

Instead of making a callback, you can get the result string by using the await keyword.

// First asynchronous call, to get an access token.
WebClient proxy = new WebClient();
String token = await proxy.DownloadStringTaskAsync(
    new Uri(facebookUri.ToString()));

Note that I used a different method here: DownloadStringTaskAsync. This method doesn’t exist in Silverlight today; it’s a part of the CTP release only. In addition to new keywords in the C# and Visual Basic languages, future versions of the .NET Framework and Silverlight need to implement the Task-based Asynchronous Pattern (TAP). The details about this pattern are also included into the CTP release; look for “The Task-based Asynchronous Pattern” document.

According to this pattern, asynchronous methods should follow a certain naming convention: They should have the “Async” postfix (or “TaskAsync” if a method with the “Async” postfix already exists in the given API, as with the WebClient class). Such methods return instances of the Task or Task<TResult> class from the Task Parallel Library. But once again, the compiler does some work for you when you use the await keyword. In my example, the type of the token variable is not Task<String> , but just String; no extra work such as conversion or type casting is necessary.

And now you can simply continue writing your application logic in the same method! Everything I had in the OnDownloadCompleted_Token method can be moved to the GetFriends method. I don’t even need to create a new WebClient, since I already have one.

So, the code

// Second asynchronous call, to get a list of friends.
WebClient proxy = new WebClient();
proxy.DownloadStringCompleted +=
    new DownloadStringCompletedEventHandler(OnDownloadCompleted_Friends);
var facebookFriendsUri = new StringBuilder(
    @"https://graph.facebook.com/me/friends?fields=id,name&");
facebookFriendsUri.Append((String)e.Result);
proxy.DownloadStringAsync(new Uri(facebookFriendsUri.ToString()));

from the OnDownloadCompleted_Token() method can be replaced with the following code (once again, using the new await keyword) and moved to the GetFriends method.

// Second asynchronous call, to get a list of friends.
var facebookFriendsUri = new StringBuilder(
    @"https://graph.facebook.com/me/friends?fields=id,name&");
facebookFriendsUri.Append(token);
String friends = await proxy.DownloadStringTaskAsync(
    new Uri(facebookFriendsUri.ToString()));

After that I can simply delete the OnDownloadCompleted_Token method. In fact, I can make everything even shorter. I don’t need to create the token variable, because I can use the await keyword right in the method call, like this:

var facebookFriendsUri = new StringBuilder(
    @"https://graph.facebook.com/me/friends?fields=id,name&");
WebClient proxy = new WebClient();
// First asynchronous call, to get an access token.
facebookFriendsUri.Append(await proxy.DownloadStringTaskAsync(
    new Uri(facebookUri.ToString())));
// Second asynchronous call, to get a list of friends.
String friends = await proxy.DownloadStringTaskAsync(
    new Uri(facebookFriendsUri.ToString()));

The last step is to simply move the code from the OnDownloadCompleted_Friends method to the GetFriends method and delete the OnDownloadCompleted_Friends method.

Here’s what the final version of the GetFriends method looks like:

public async void GetFriends()
{
    var code = HtmlPage.Document.DocumentUri.Query.TrimStart('?');
    var facebookUri = new StringBuilder(
        @"https://graph.facebook.com/oauth/access_token?");
    facebookUri.Append("client_id=").Append(client_id)
        .Append(@"&redirect_uri=").Append(redirect_uri)
        .Append(@"&client_secret=").Append(client_secret)
        .Append('&').Append(code);

    var facebookFriendsUri = new StringBuilder(
        @"https://graph.facebook.com/me/friends?fields=id,name&");

    WebClient proxy = new WebClient();

    // First asynchronous call, to get an access token.
    facebookFriendsUri.Append(await proxy.DownloadStringTaskAsync(
        new Uri(facebookUri.ToString())));

    // Second asynchronous call, to get a list of friends.
    String friends = await proxy.DownloadStringTaskAsync(
        new Uri(facebookFriendsUri.ToString()));

    JsonObject jsonObject = (JsonObject)JsonValue.Parse(friends);
    JsonArray jsonArray = (JsonArray)jsonObject["data"];

    foreach (var friend in jsonArray)
    {
        searchResults.Add(new FacebookFriend() {
            Name = friend["name"], ID = friend["id"] });
    }
}

Instead of three methods I have just one, and it’s much more straightforward and easier to read. It simply has less code to begin with. I hope that I gave you just enough information to encourage you to explore more on your own and read more about the Visual Studio Async CTP.

Comments

  • Anonymous
    October 28, 2010
    > var facebookAuthUri = new StringBuilder(@"graph.facebook.com/.../authorize);        facebookAuthUri.Append("client_id=").Append(client_id).Append@"&redirect_uri=").Append(redirect_uri); This is an anti-pattern in c# - the much clearer string+ operator will compile into code that's just as efficient. (of course, string.format() would be the clearest.) What about thread affinity and sync context issues - we can guess if it's like f#, but could explain.

  • Anonymous
    October 28, 2010
    I've just heard about the await keyword and this was the first blog entry I read when trying to understand it. What I don't understand, however, is how is this different to doing things synchronously? The example looks like a lot of syntax for turning an asynchronous call into a synchronous one, since it won't continue until the web method returns. Isn't this what we're trying to avoid?

  • Anonymous
    October 28, 2010
    Ah, await works like yield. Got it

  • Anonymous
    October 28, 2010
    I get 90% of what is going on here... GetFriends() is essentially running in the original thread... at least until the first await is called, because then it returns Task.    Then it continues in a call back.   Now, isn't the callback essentially asynchronous? Eventually "searchResults" is modified, which something in the display has to be looking at...   How does this synchronize?   Does the caller have to check to see if GetFriends() has completed and then refresh the display?

  • Anonymous
    October 29, 2010
    Jim T, I think the point is that since the GetFriends method is marked as async, the Page_Load will return immediately (thus allowing the page to render). Then once the GetFriends method has completed it will asynchronously update the friends list. I guess the benefit of these keywords is it allows you to work in an almost synchronous way, thus simplifying your code.

  • Anonymous
    October 29, 2010
    That's what I was saying wasn't being explained in this case. There are gobs of articles on F# async, so that's a good place to look.  Basically, during the await, your thread not block as it would be in a sync call, but is off doing other things, like handling other Task<>s or servicing your application message pump, etc.  What needs to be explained is interaction with the GUI thread and the sync context - you need to know if (assuming a gui app) you are (still) on the GUI thread after the async so that you can interact w/the GUI w/o a Control.Invoke (winforms) or SyncContext.Post (wpf).

  • Anonymous
    October 29, 2010
    The comment has been removed

  • Anonymous
    November 03, 2010
    Please please please make this feature work on all platforms, including Silverlight and Windows Phone 7 apps. Not sure if TPL is supported on Windows Phone, but it would be a shame for a language feature to work only on certain platforms and not others. <TwoCents/>

  • Anonymous
    November 04, 2010
    the Visual Studio Async CTP works only with US-version of VisualStudio 2010. Is there any intention to release a language-free version next time? On German VisualStudio 2010 is doesn't work.

  • Anonymous
    November 04, 2010
    @Dan Right now TPL is not supported in SIlverlight, you are right. But as you can see from the samples, Silverlight is definitely one of the areas where we'd like to see it work. @Roy Unfortunately, this is a known issue and there is no fix for it. This CTP version works only with en-us version of Visual Studio 2010 social.msdn.microsoft.com/.../2fd94da2-aaf6-475b-870b-0e5931272ed8

  • Anonymous
    November 05, 2010
    For async enumeration, is the standard way going to be an IEnumerable<Task<T>>?  Or will you be introducing a more preferable IAsyncEnumerator<T> with a Task-returning MoveNext?  Could open up some interesting patterns like "foreach(await var x in asyncenumerable)".

  • Anonymous
    November 28, 2010
    Why just not providing a dynamic parser, one where syntax can be extended like in Boo? This async thing could be simply resolved by using that. It seems to me that a dynamic parser is the natural step after dynamic languages.

  • Anonymous
    January 15, 2011
    Is this feature similar to node.js asynchronous programming model ?

  • Anonymous
    February 27, 2011
    Hi I downloaded Visual Studio Async CTP but I get this error during installation: Could not load type "System.Windows.Input.FocusNavigationDirection" from assembly 'WindowsBase, Version=4.0.0.0, culture=neutral, PublicKeyToken=31bf3856ad36e35' due to value type mismatch. Then when I try to open VS 2010 I get the same error and VS is closed automatically. The funnies thing is the installation finished with a "Successfully message" and the installation log file is the same, I looked to my GAC and WindowsBase assembly existing but version 3.0.0.0, I believe this is the problem, it looks like this CTP is looking for version 4, Am I correct ? Thanks for your help Robert

  • Anonymous
    November 02, 2011
    The comment has been removed

  • Anonymous
    November 02, 2011
    The comment has been removed

  • Anonymous
    March 20, 2013
    Seems I need to update my knowledge again.

  • Anonymous
    July 11, 2013
    Excellent Post. Also visit http://www.msnetframework.com/

  • Anonymous
    February 24, 2014
    I have a question: What about MOBILE DEVICE application development (NO, I'm NOT talking about PHONES!!!!  Apparently, MS is not aware that not ALL "mobile devices"--are phones!!!)  How or when will we be able to upgrade our mobile device applications from VS2008 to VS2010 or VS2012 or VS2013, etc.?