Simplify asynchronous programming with C# 5 async/await

One of key benefit of asynchronous programming is scalable & responsive applications. On server by eliminating threads blocked for network/disk I/O to complete, you can write scalable application using fewer server resources (threads). Similarly on the client side asynchronous programming helps in designing responsive UI without blocking the UI thread.

Traditionally designing asynchronous programming is hard that involves learning low level operating systems facilities such as POSIX select, epoll on Linux and IO completion ports on Windows. Even though newer platforms (Java and .NET) hide these complexities with nice object oriented API it is still hard to develop asynchronous applications as it involves dealing with callback and state machines.

.NET applications use APM or EAP to write asynchronous applications. Throughout .NET base class library, many classes support using the APM by implementing BeginXXX and EndXXX versions of functions. These methods allow applications to perform I/O operations asynchronously. For example, the FileStream class in System.IO namespace has a BeginXXX and EndReadXXX methods that reads data from a stream asynchronously.

Even with APM, asynchronous application development is still not as easy as synchronous applications because of callback and the state involved. C# 5 introduces new language features to simplify asynchronous programming. It introduces pattern where user can write asynchronous programs with sequential control flow by using async/await keywords. C#  implements this feature using Task Parallel library (TPL) and some compiler magic. If you interested to know more about how these feature implemented see the resource section.

Here is the simple TCP echo server that demonstrates C# async/await pattern .This program handle multiple client connections without creating threads explicitly. If you read the program, it looks like synchronous application with sequential control flow and easy to understand and maintain. Under the cover C# compiler is transforming code to use the asynchronous API with the help of Task parallel library. This transformation is similar to how C# implements yield keyword

 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncEchoServer
{
    public class AsyncEchoServer
    {
        private int _listeningPort;
        public AsyncEchoServer( int port)
        {
            _listeningPort = port;
        }
        ///
<summary>
        /// Start listening for connection
        /// </summary>
        public async void Start()
        {
            IPAddress ipAddre = IPAddress.Loopback;
            TcpListener listener = new TcpListener(ipAddre, _listeningPort);
            listener.Start();
            LogMessage("Server is running");
            LogMessage("Listening on port " + _listeningPort);

            while (true)
            {
                LogMessage("Waiting for connections...");
                try
                {
                    var tcpClient = await listener.AcceptTcpClientAsync();
                     HandleConnectionAsync(tcpClient);
                }
                catch (Exception exp)
                {
                    LogMessage(exp.ToString());
                }

            }

        }
        ///
<summary>
        /// Process Individual client
        /// </summary>
        ///
        ///
        private async void HandleConnectionAsync(TcpClient tcpClient)
        {
            string clientInfo = tcpClient.Client.RemoteEndPoint.ToString();
            LogMessage(string.Format("Got connection request from {0}", clientInfo));
            try
            {
                using (var networkStream = tcpClient.GetStream())
                using (var reader = new StreamReader(networkStream))
                using (var writer = new StreamWriter(networkStream))
                {
                    writer.AutoFlush = true;
                    while (true)
                    {
                        var dataFromServer = await reader.ReadLineAsync();
                        if (string.IsNullOrEmpty(dataFromServer))
                        {
                            break;
                        }
                        LogMessage(dataFromServer);
                        await writer.WriteLineAsync("FromServer-" + dataFromServer);
                    }
                }
            }
            catch (Exception exp)
            {
                LogMessage(exp.Message);
            }
            finally
            {
                LogMessage(string.Format("Closing the client connection - {0}",
                            clientInfo));
                tcpClient.Close();
            }

        }
        private void LogMessage(string message,
                                [CallerMemberName]string callername = "")
        {
            System.Console.WriteLine("[{0}] - Thread-{1}- {2}",
                    callername, Thread.CurrentThread.ManagedThreadId, message);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            AsyncEchoServer async = new AsyncEchoServer(51510);
            async.Start();
            Console.ReadLine();
        }
    }
}

Echo Client Sample

class Program
    {
        static void Main(string[] args)
        {
            StartClient(Convert.ToInt32(args[0]));
            Console.ReadLine();
        }

        private static async void StartClient(int port)
        {
            TcpClient client = new TcpClient();
            await client.ConnectAsync(IPAddress.Loopback, port);
            LogMessage("Connected to Server");
            using (var networkStream = client.GetStream())
            using (var writer = new StreamWriter(networkStream))
            using (var reader = new StreamReader(networkStream))
            {
                writer.AutoFlush = true;
                for(int i = 0; i < 10;i++)
                {
                    await writer.WriteLineAsync(DateTime.Now.ToLongDateString());
                    var dataFromServer = await reader.ReadLineAsync();
                    if (!string.IsNullOrEmpty(dataFromServer))
                    {
                        LogMessage(dataFromServer);
                    }
                    
                }
            }
            if (client != null)
            {
                client.Close();
            }
           
        }
        private static void LogMessage(string message, 
                [CallerMemberName]string callername = "")
        {
            System.Console.WriteLine("{0} - Thread-{1}- {2}", 
                callername, Thread.CurrentThread.ManagedThreadId, message);
        }

    }

Resource

1. Visual Studio async home page.

2. DNT TV Screencast which explains async feature in .NET .

3. Jon Skeet multi part screencast on async/await. Part 1 , Part 2

4. Joseph Albahari talk at Tech·Ed Australia 2011

Advertisements