Vlad's blog

In programming veritas

async/await in C#

leave a comment »

Concurrency is mandatory part of almost every modern application. In .Net TPL provides solid foundation for organizing different scenarios of asynchronous communication. Starting from MSVS 2012 we have two new keywords to deal with concurrent methods: async and await.

As example consider client server remote communication. The Server maintains a list of jobs that Client must execute. Once client is successfully authorized it starts retrieving jobs one by one. When a job is finished the Client asks the Server to remove this job from the list and retrieves the next job. All communication between Server and Client is asynchronous because the nature of communication protocol. But the logic is definitely sequential:

  • Authorize
  • Retrieve first job
  • Execute first job
  • Finalize first job on the Server
  • Retrieve the next job

Server
We want to update the UI in non blocking fashion. So if authorization takes 3 seconds the UI should be responsible. Below is the implementation of sample Authentication service. It just adds a delay to simulate server side processing.

public class AuthenticationService
{
   public Task<bool> Authenticate()
   {
     return Task.Delay(TimeSpan.FromSeconds(3)).ContinueWith(t => true);
   }
 }


Job service is defined below.

public class JobService
{
   readonly ConcurrentQueue<JobDto> jobs;

   public JobService()
   {
     this.jobs = new ConcurrentQueue<JobDto>();

     this.jobs.Enqueue(new JobDto("Data 1"));
     this.jobs.Enqueue(new JobDto("Data 2"));
     this.jobs.Enqueue(new JobDto("Data 3"));
   }

   public Task<JobDto> GetNextJob()
   {
     JobDto job;
     this.jobs.TryPeek(out job);

     return Task.FromResult(job);
   }

   public Task CompleteNextJob()
   {
     JobDto jobDto;
     this.jobs.TryDequeue(out jobDto);

     if (jobDto == null)
     {
       throw new Exception("JobService.CompleteJob: unable to perform the operation: queue is empty");
     }

     return Task.Delay(TimeSpan.FromSeconds(1));
   }
 }

Server returns definition of job as JobDto object.

public class JobDto
{
    public JobDto(string data)
    {
        this.Data = data;
    }

    public string Data { get; private set; }
}

Client

Client implementation is defined in method Run which is marked as async to indicate that we are going to use await keyword in the method body. The first step is authentication.

private static async void Run()
{
 Console.WriteLine("Started application");
 var authService = new AuthenticationService();

 Console.WriteLine("Started authentication");
 bool isAuthenticated = await authService.Authenticate();

 if (isAuthenticated != true)
 {
   throw new AuthenticationException("Authentication failed");
 }

 ...
}

The code is executed in the main thread until we reach line 7 where we invoke authentication procedure. The method AuthenticationService.Authenticate() returns TPL task which runs in the background. At the same time the method Run() exits and application starts executing the next statement which follows the method Run() in the main thread namely Console.WriteLine(“Press any key to exit”).

static void Main(string[] args)
{
  Run();
  Console.WriteLine("Press any key to exit");
  Console.ReadKey();
}

When Authenticate() completes the program resumes its execution from line 9 where we check isAuthenticated flag. But now this is different thread.
Below is the stack trace in the breakpoint at line 7 where we are in the main thread.
Image1

See that stack trace is different when we reached line 9.
Image2

Actually the compiler generates continuation code that is invoked after authentication task is finished. We can achieve the same goal with traditional Task.ContinueWith() method.

Console.WriteLine("Started authentication");

authService.Authenticate().ContinueWith(task =>
{
  bool isAuthenticated = task.Result;

  if (isAuthenticated != true)
  {
    throw new AuthenticationException("Authentication failed");
  }

  Console.WriteLine("Successfully authenticated");

  ...
});

Below is the program output.
output
But what if we have more than one asynchronous call or we need to do that in the loop? Below is the complete implementation of Run() method.

private static async void Run()
{
    Console.WriteLine("Started application");
    var authService = new AuthenticationService();

    Console.WriteLine("Started authentication");

    bool isAuthenticated = await authService.Authenticate();

    if (isAuthenticated != true)
    {
        throw new AuthenticationException("Authentication failed");
    }

    Console.WriteLine("Successfully authenticated");

    var dataService = new JobService();

    while (true)
    {
        JobDto job = await dataService.GetNextJob();

        if (job == null)
        {
            break;
        }

        Console.WriteLine("Executing job: " + job.Data);

        await dataService.CompleteNextJob();
    }

    Console.WriteLine("Finished application");
}

Certainly the compiler does really good job generating all these code for us. The next thing I want to mention is the fact that continuation is invoked at the main thread so we don’t need to take care of serialization to the calling thread which is important for many scenarios that require UI update. Internally there is a synchronization context which is used to serialize the continuation code in the calling thread even if a task was executed in the different thread.

Exceptions

What if during execution of TPL task we raise an exception? Say if CompleteNextJob() will throw an exception?

public Task CompleteNextJob()
{
    throw new Exception("JobService.CompleteJob: unable to perform the operation: queue is empty");
}

This exception must be handled by a try/catch block in the Run method() (I skipped some code to make it more clean).

private static async void Run()
{
    try
    {
        ...

        var dataService = new JobService();

        while (true)
        {
            JobDto job = await dataService.GetNextJob();

            if (job == null)
            {
                break;
            }

            Console.WriteLine("Executing job: " + job.Data);

            await dataService.CompleteNextJob();
        }

        Console.WriteLine("Finished application");
    }
    catch (Exception)
    {
        Console.WriteLine("Exception caught");
    }
}

Again, the code looks like traditional synchronous code. When you use TPL you should handle AggregatedException in the place where you harvest the result or in Task.Wait() method. You don’t need to do this if you use async/await approach.
output

Advertisements

Written by vsukhachev

November 24, 2014 at 1:21 pm

Posted in Development

Tagged with

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: