Asynchronous programming using Tasks

Asynchronous programming model allows you to run and complete several tasks in a parallel way. Using this model can make your applications more responsive (non-blocking) but make no mistake, it’s a tricky road to follow. There are few different models for asynchronous programming such as the old APM Asynchronous Programming Model, the EAP Event-based Asynchronous Pattern and latest one, the TAP TASK-based Asynchronous Pattern which is also the subject of this post. The main class used in TAP pattern is the System.Threading.Tasks.Task which represents asynchronous operations. Asynchronous methods return either Task or Task<T> objects which in turn is not the real result but an ongoing task that is going to be completed at some point. You retrieve the actual result from a Task using it’s “Result” property. Let us see some real examples to understand how the TAP asynchronous model works. I will use the Chinook database which you can download and install it to your SQL Server from here. It’s not necessary though, you can adjust the respective queries we will write to point your preferred database tables.
asynchronous-tap-00
Create a new Console application in Visual Studio (i used 2012 version and .NET 4.5) and open the default Program.cs file where the main method is. Add the following asynchronous method which returns a Task object containing the top 10 records from the Artist table.

static Task<DataSet> GetArtistsAsync()
        {
            DataSet ds = new DataSet();
            return Task<DataSet>.Factory.StartNew(() =>
            {
                using (SqlConnection con = new SqlConnection(connectionString))
                {
                    string sqlSelect = @"WAITFOR DELAY '000:00:05'
                                        SELECT TOP 10 * FROM Artist";
                    SqlDataAdapter da = new SqlDataAdapter(sqlSelect, con);
                    da.Fill(ds);
                    ds.Tables[0].TableName = "Artists";
                }
                Console.WriteLine("Thread: " + Thread.CurrentThread.Name);
                return ds;
            });
        }

On purpose we set a 5 seconds delay so we can see how the method behaves. We use the StartNew function to start and create a new Task, passing to it a delegate as a parameter. Now let’s call this method in the main method.

class Program
    {

        const string connectionString = "Data source = localhost; Initial catalog = Chinook; Integrated security = SSPI;";

        static void Main(string[] args)
        {
            Thread.CurrentThread.Name = "MainThread";
            #region This will block the thread...
            DataSet dsArtists = GetArtistsAsync().Result;
            foreach (DataRow row in dsArtists.Tables["Artists"].Rows)
            {
                foreach (DataColumn col in dsArtists.Tables[0].Columns)
                {
                    Console.Write(row[col] + "\t");
                }
                Console.WriteLine();
            }
            #endregion

            Console.WriteLine();
            Console.WriteLine("Thread: " + Thread.CurrentThread.Name);
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }

If you build and run your application you will get the following result.
asynchronous-tap-01
Let’s see what actually happened here. Not what we intended that’s for sure. What we wanted to see is while waiting for the GetArtistsAsync() to return, continue our execution in the main method, that is to see the content in the red box in the above picture to be printed first and then the results from our asynchronous operation. The reason why this happened is that you requested the GetArtistsAsync().Result directly. This in turn, blocked the current thread and returned only when it’s operation completed. Another important point to mention is that the Task.Factory.StartNew created a new thread which means we used multi-threading (that’s why they had different names), something you don’t always want to. We will get back to this issue later on this post. Let’s now see how to call the previous operation in an asynchronous way. Comment out or remove the “#region This will block the thread” and replace it with the following one.

#region This won't block the main thread
            GetArtistsAsync().ContinueWith(task =>
            {
                DataSet dsArtists = task.Result;
                foreach (DataRow row in dsArtists.Tables["Artists"].Rows)
                {
                    foreach (DataColumn col in dsArtists.Tables[0].Columns)
                    {
                        Console.Write(row[col] + "\t");
                    }
                    Console.WriteLine();
                }
            });
#endregion

If you run your application again you will get the following result.
asynchronous-tap-02
Now indeed we didn’t wait for the asynchronous method to complete but we proceed our execution in the main method. When the operation completed we printed the results. We accomplished that using the ContinueWith() which creates a continuation that executes when the target Task completes. In other words, when we read the task.Result property inside it’s delegate, we are sure that the task has completed. In the ContinueWith() function you can pass some extra parameters in order to proceed with the returned Task depending to it’s final Status. For example, let’s on purpose change the select statement in the GetArtistsAsync so it fails.

string sqlSelect = @"WAITFOR DELAY '000:00:05'
                     SELECT TOP 10 * FROM ArtistFail";

If you run now your application, you will see printed only the red box contents. Run it in debug mode if you want to see the Exception results.
asynchronous-tap-03
Notice that the task.Status was Faulted. You can tell inside the ContinueWith() function what you want to do depending from the task Status as follow.

#region This won't block the thread and catches exceptions
            GetArtistsAsync().ContinueWith(task =>
            {
                DataSet dsArtists = task.Result;
                foreach (DataRow row in dsArtists.Tables["Artists"].Rows)
                {
                    foreach (DataColumn col in dsArtists.Tables[0].Columns)
                    {
                        Console.Write(row[col] + "\t");
                    }
                    Console.WriteLine();
                }
            }, TaskContinuationOptions.NotOnFaulted);

            GetArtistsAsync().ContinueWith(task =>
            {
                Console.WriteLine(task.Exception.InnerException.Message);
            }, TaskContinuationOptions.OnlyOnFaulted);

            #endregion

asynchronous-tap-04
Now let’s try something more interesting. Assume that you have several tasks, and you want to proceed with the main execution till all of them have completed. Add a new asynchronous method which returns the top 10 album records.

static Task<DataSet> GetAlbumsAsync()
        {
            DataSet ds = new DataSet();
            return Task<DataSet>.Factory.StartNew(() =>
            {
                using (SqlConnection con = new SqlConnection(connectionString))
                {
                    string sqlSelect = @"WAITFOR DELAY '000:00:05'
                                        SELECT TOP 10 * FROM Album";
                    SqlDataAdapter da = new SqlDataAdapter(sqlSelect, con);
                    da.Fill(ds);
                    ds.Tables[0].TableName = "Albums";
                }
                Console.WriteLine("Thread: " + Thread.CurrentThread.Name);
                return ds;
            });
        }

Each operation needs at least five seconds to complete but mind that they are going to run in different threads. You use the Task.Factory.ContinueWhenAll method to wait for several tasks to be completed as follow.

#region Task Composition - Multithreading
            var watch = Stopwatch.StartNew();

            Task<DataSet> artistsTask = GetArtistsAsync();
            Task<DataSet> albumsTask = GetAlbumsAsync();

            Task.Factory.ContinueWhenAll(new[] { artistsTask, albumsTask }, (tasks) =>
            {
                foreach (var task in tasks)
                {
                    if (task.Status == TaskStatus.RanToCompletion)
                    {
                        DataSet ds = task.Result;
                        if (ds.Tables[0].TableName == "Artists")
                        {
                            foreach (DataRow row in ds.Tables["Artists"].Rows)
                            {
                                foreach (DataColumn col in ds.Tables[0].Columns)
                                {
                                    Console.Write(row[col] + "\t");
                                }
                                Console.WriteLine();
                            }
                        }
                        else if (ds.Tables[0].TableName == "Albums")
                        {
                            foreach (DataRow row in ds.Tables["Albums"].Rows)
                            {
                                foreach (DataColumn col in ds.Tables[0].Columns)
                                {
                                    Console.Write(row[col] + "\t");
                                }
                                Console.WriteLine();
                            }
                        }
                    }
                    else
                    {
                        Console.WriteLine("An error has occurred..");
                        Console.WriteLine(task.Exception.InnerException.Message);
                    }
                    Console.WriteLine();
                    Console.WriteLine("------------------------------------------------");
                    Console.WriteLine();
                }

                watch.Stop();
                Console.WriteLine("Time elapsed: " + watch.ElapsedMilliseconds + " milliseconds");
            });
            #endregion

Here we print the results only and only if the task was completed successfully. Take a look at the time elapsed for the tasks to execute.
asynchronous-tap-05
Now let’s return to an issue mentioned before. Do we really want a different thread for our asynchronous operations? Well, some times yes but others no. So how we can accomplish this? We use the TaskCompletionSource class instead of the Task.Factory.StartNew method. Create a new asynchronous operation which returns the top 10 Customer records as follow.

static Task<DataSet> GetCustomersAsync()
        {
            var tcs = new TaskCompletionSource<DataSet>();

            DataSet ds = new DataSet();

            using (SqlConnection con = new SqlConnection(connectionString))
            {
                string sqlSelect = @"WAITFOR DELAY '000:00:05'
                                        SELECT TOP 10 * FROM Customer";
                SqlDataAdapter da = new SqlDataAdapter(sqlSelect, con);
                da.Fill(ds);
                ds.Tables[0].TableName = "Customers";
            }
            Console.WriteLine("Thread in GetCustomersAsync: " + Thread.CurrentThread.Name);
            tcs.SetResult(ds);
            return tcs.Task;
        }

You can call it in the same way we called the others.

#region Asynchronous - No multithreading
            GetCustomersAsync().ContinueWith((task) =>
            {
                DataSet dsCustomers = task.Result;
                foreach (DataRow row in dsCustomers.Tables["Customers"].Rows)
                {
                    foreach (DataColumn col in dsCustomers.Tables[0].Columns)
                    {
                        Console.Write(row[col] + "\t");
                    }
                    Console.WriteLine();
                }
            });

            #endregion

asynchronous-tap-06
This is the optimal way to call asynchronous methods without using multi-threading. That’s it, we saw some really interesting things in this post about asynchronous programming with the TAP model. I hope you enjoyed it, you can download the project we built from here.



Categories: Best practices

Tags: , ,

6 replies

  1. I feel this is among tthe such a lot significant information for
    me. And i’m glad reading your article. However
    want to statement on few basic issues,Thhe web site taste is ideal, the
    articles is actually nice : D. Good task, cheers

  2. Hi thete to all, how iss the whole thing, I thjnk
    every onee is getting ore from this website, and your views are njce in favor of new users.

  3. Best article on Aynnc data select

  4. Perhaps you can try this nuget package which implements async/await behavior as you would expect: https://github.com/voloda/AsyncDataAdapter

  5. thanks a lot my brother hello from Türkiye

Leave a Reply to dog has pain in back legs Cancel 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 )

Facebook photo

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

Connecting to %s

%d bloggers like this: