Want to understand async

Sam picture Sam · Nov 25, 2014 · Viewed 23.4k times · Source

I've used async coding a little bit but I don't really fully understand how to use it -- though I understand the concept and why I need it.

Here's my set up:

I have a Web API that I will call from my ASP.NET MVC app and my Web API will call DocumentDB. In code samples, I see a lot of await keywords while sending queries to DocumentDB.

I'm confused if I need to make my Index action method in my MVC app async? I'm also confused if my CreateEmployee() method in my Web API should be async?

What is the right way to use async in this scenario?

Here's my code (This code is currently giving me errors because my MVC action method is not async) ---- ASP.NET MVC App Code ----

public ActionResult Index()
{

   Employee emp = new Employee();
   emp.FirstName = "John";
   emp.LastName = "Doe";
   emp.Gender = "M";
   emp.Ssn = "123-45-6789";

   using (var client = new HttpClient())
   {
      client.BaseAddress = new Uri("http://myWebApi.com");
      client.DefaultRequestHeaders.Accept.Clear();
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

      HttpResponseMessage response = await client.PostAsJsonAsync("hr/create/newemployee", emp);
      if (response.IsSuccessStatusCode)
      {
         emp = await response.Content.ReadAsAsync<Employee>();
      }
   }

   // Display employee info
   return View(emp);
}

---- Web API Code ----

private static readonly string endPointUrl = ConfigurationManager.AppSettings["EndPointUrl"];
private static readonly string authorizationKey = ConfigurationManager.AppSettings["AuthorizationKey"];
private static readonly string databaseId = ConfigurationManager.AppSettings["DatabaseId"];
private static DocumentClient client;

public static async Task<Employee> CreateEmployee(Employee emp)
{
   try
   {
      //Create a Document client
      using (client = new DocumentClient(new Uri(endPointUrl), authorizationKey))
      {
         //Get the database
         var database = await GetDatabaseAsync();

         //Get the Document Collection
         var collection = await GetCollectionAsync(database.SelfLink, "Employees");

         await client.CreateDocumentAsync(collection.SelfLink, emp);

         // Further process employee
       }
    }
    catch
    {
       // Handle error
    }

    return employee;
}

private static async Task<DocumentCollection> GetCollectionAsync(string dbLink, string id)
{
   DocumentCollection collection = client.CreateDocumentCollectionQuery(dbLink).Where(c => c.Id == id).ToArray().FirstOrDefault();

   return collection;
}

private static async Task<Database> GetDatabaseAsync()
{
   Database database = client.CreateDatabaseQuery().Where(db => db.Id == databaseId).ToArray().FirstOrDefault();

   return database;
}

Answer

Lu4 picture Lu4 · Nov 25, 2014

Here's my explanation

class MainClass
{
    public static async Task<String> AsyncMethod(int delay) {

        await Task.Delay (TimeSpan.FromSeconds(delay));

        return "The method has finished it's execution after waiting for " + delay + " seconds";
    }

    public static async Task Approach1(int delay)
    {
        var response = await AsyncMethod (delay); // await just unwraps Task's result

        Console.WriteLine (response);
    }

    public static Task Approach2(int delay)
    {
        return AsyncMethod(delay).ContinueWith(message => Console.WriteLine(message)); // you could do the same with 
    }

    public static void Main (string[] args)
    {
        var operation1 = Approach1 (3);
        var operation2 = Approach2 (5);

        Task.WaitAll (operation1, operation2);

        Console.WriteLine("All operations are completed")
    }
}

Eventually both Approach1 and Approach2 are identical pieces of code.

The async/await is syntactic sugar around Task API. It takes your async method splits it into parts before await, and after await. The "before" part is executed immediately. The "after" part is getting executed when await operation is completed. You are able to track the second part of operation via the Task API since you get a reference to a Task.

In general async allows to treat a method call as a some sort of long operation that you can reference via the Task API and wait until it is finished and continue with another piece of code. Either via ContinueWith call of via using await in general it's the same.

Before async/await/Task concepts people were using callbacks, but handling errors was as easy as hell, the Task is similar to a concept of callback except that it is able allow handling exceptions more easily.

In general all this Task/async/await mantra is close to concept of promises if it happen that you've worked with jQuery/JavaScript there's a similar concept here's a nice question explaining how it's done there "jQuery deferreds and promises - .then() vs .done()"


Edit: I've just found out that .NET lacks implementation of then functionality similar to one found in jQuery/JavaScript.

The difference between ContinueWith and Then is that Then is able to compose task, and to execute them sequentially while ContinueWith is not, it is able only to launch task in parallel, but it can be easily implemented via the await construct. Here is my updated code containing the whole shebang:

static class Extensions
{
    // Implementation to jQuery-like `then` function in .NET
    // According to: http://blogs.msdn.com/b/pfxteam/archive/2012/08/15/implementing-then-with-await.aspx
    // Further reading: http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx
    public static async Task Then(this Task task, Func<Task> continuation) 
    { 
        await task; 
        await continuation(); 
    } 

    public static async Task<TNewResult> Then<TNewResult>( 
        this Task task, Func<Task<TNewResult>> continuation) 
    { 
        await task; 
        return await continuation(); 
    } 

    public static async Task Then<TResult>( 
        this Task<TResult> task, Func<TResult,Task> continuation) 
    { 
        await continuation(await task); 
    } 

    public static async Task<TNewResult> Then<TResult, TNewResult>( 
        this Task<TResult> task, Func<TResult, Task<TNewResult>> continuation) 
    { 
        return await continuation(await task); 
    }
}

class MainClass
{
    public static async Task<String> AsyncMethod1(int delay) {

        await Task.Delay (TimeSpan.FromSeconds(delay));

        return "The method has finished it's execution after waiting for " + delay + " seconds";
    }

    public static Task<String> AsyncMethod2(int delay)
    {
        return Task.Delay (TimeSpan.FromSeconds (delay)).ContinueWith ((x) => "The method has finished it's execution after waiting for " + delay + " seconds");
    }

    public static async Task<String> Approach1(int delay)
    {
        var response = await AsyncMethod1 (delay); // await just unwraps Task's result

        return "Here is the result of AsyncMethod1 operation: '" + response + "'";
    }

    public static Task<String> Approach2(int delay)
    {
        return AsyncMethod2(delay).ContinueWith(message => "Here is the result of AsyncMethod2 operation: '" + message.Result + "'");
    }

    public static void Main (string[] args)
    {
        // You have long running operations that doesn't block current thread
        var operation1 = Approach1 (3); // So as soon as the code hits "await" the method will exit and you will have a "operation1" assigned with a task that finishes as soon as delay is finished
        var operation2 = Approach2 (5); // The same way you initiate the second long-running operation. The method also returns as soon as it hits "await"

        // You can create chains of operations:
        var operation3 = operation1.ContinueWith(operation1Task=>Console.WriteLine("Operation 3 has received the following input from operation 1: '" + operation1Task.Result + "'"));
        var operation4 = operation2.ContinueWith(operation2Task=>Console.WriteLine("Operation 4 has received the following input from operation 2: '" + operation2Task.Result + "'"));

        var operation5 = Task.WhenAll (operation3, operation4)
            .Then(()=>Task.Delay (TimeSpan.FromSeconds (7)))
            .ContinueWith((task)=>Console.WriteLine("After operation3 and 4 have finished, I've waited for additional seven seconds, then retuned this message"));

        Task.WaitAll (operation1, operation2); // This call will block current thread;

        operation3.Wait (); // This call will block current thread;
        operation4.Wait (); // This call will block current thread;
        operation5.Wait (); // This call will block current thread;

        Console.WriteLine ("All operations are completed");
    }
}