Using TPL and Async and Await

发布于 - 最后修改于

The TPL (Task Parallel Library) was introduced in .NET Framework 4.0 and offers an alternative to creating multithreaded applications without working directly with Thread, BackgroundWorker and Async Delegates.

The Parallel class

The Parallel class was created for easier parallelization of computational work. It has three methods:

            Parallel.For(0L, 
                         80000000L, 
                         (item) => 
                                { 
                                    if(item % 800 == 0) 
                                    {
                                        Console.WriteLine(item);
                                    }
                                });

            var numbers = Enumerable.Range(0, 80000000);
            Parallel.ForEach<int>(numbers,
                            (item) =>
                            {
                                if (item % 800 == 0)
                                {
                                    Console.WriteLine(item);
                                }
                            });

The Parallel.For method is basically the same as a traditional for loop, but executes the lambda expression on multiple threads behind the scenes to maximize the usage of CPU. It doesn’t matter if you have two or four or eight CPU cores—this implementation will make sure to parallelize the action between them to use all the available CPU cores.

The Parallel.ForEach is a variant of the normal foreach loop. The first argument is the collection that needs to be processed, while the second argument is an action (or a lambda expression) which is executed for each item. As in the case of Parallel.For() this method is also optimized to use all the power of the CPU to do the operations.

The Parallel class’ third method is called Invoke(). It can receive any number of actions parameters (or lambda expressions). It executes each expression on a different thread:

Parallel.Invoke(
                () => 
                    { 
                        Console.WriteLine("First Action. ThreadId:{0}", System.Threading.Thread.CurrentThread.ManagedThreadId); 
                    },
                () => 
                    { 
                        Console.WriteLine("Second Action. ThreadId:{0}", System.Threading.Thread.CurrentThread.ManagedThreadId);  
                    },
                () => 
                    { 
                        Console.WriteLine("Third Action. ThreadId:{0}", System.Threading.Thread.CurrentThread.ManagedThreadId); 
                    }
                );

If you run the above code, you will get something similar to the screenshot below. Please note that there is no guarantee that the actions are executed in the order these were passed to the Invoke() method.

parallel invoke

The Task class

The Task class will help you execute code on a new thread, but offers a wider option to create execution. 

Task.Factory.StartNew(() =>
            {
                var tmpNumbers = Enumerable.Range(1, 800);
                var sum = tmpNumbers.Sum();
                Console.WriteLine("Sum of numbers till 800 is {0}", sum);
            });

The StartNew() method creates a new Task instance and starts the execution immediately. The parameter is an action that is executed on a new Thread. The TPL library uses the ThreadPool to get threads for the execution.

Tasks can be created without the Factory:

var myTask = new Task<long>(() =>
            {
                var tmpNumbers = Enumerable.Range(1, 800);
                var sum = tmpNumbers.Sum();
                System.Threading.Thread.Sleep(1500);
                return sum;
            });

myTask.Start();

Console.WriteLine("Task has been executed, the result is: {0}", myTask.Result); 

I declared the myTask as a new instance of the Task class, and I specified the result type (in this case, long), and I passed the expression that should be executed. It is important to note that I created the task instance, but didn't start it. I needed to manually invoke the Start() method to start the execution. When I wrote myTask.Result to the Console, the main thread remained blocked until the Result had the value. To illustrate this, I added the Thread.Sleep(1500) to the task expression. You'll see how it behaves if you run the code.

Using Async and Await

In .NET Framework 4.5, the async and await keywords were introduced. These keywords will hand over the generation of the code for handling synchronization data sharing between threads to the compiler.

The way to use the two keywords is to create an async method that is executed on a second thread. With the help of the await keyword, you can stop the execution of the main thread and wait until the result is returned when the processing on the second thread finishes.

        public static async Task<int> CalculateSumAsync(IEnumerable<int> items)
        {
            var sum = items.Sum();
            return sum;
        }

        public static async void DisplaySumAsync()
        {
            int calculatedSum = await CalculateSumAsync(Enumerable.Range(1, 1500));
            Console.WriteLine("Calculated Sum: {0}", calculatedSum);
        }

The CalculateSumAsync() method is marked as async and it returns the sum of items from the IEnumerable passed in as parameter. Please note that the method’s return type is marked as Task<int> but I only return an int. This will compile since I’ve marked the method as asynchronous using the async keyword.

The DisplaySumAsync() is also an asynchronous method. In the first line, I wait for the CalculateSumAsync() method to finish the execution and write the result to the console after. The await keyword should be used in async methods, otherwise it will not help much in parallelizing the processing because it will execute synchronously. 

It's a good practice to always use the async suffix on asynchronous methods. The await and async keywords can greatly simplify how to run calculations and time consuming operations on a second thread so it doesn't block the main execution thread. 

发布于 13 四月, 2015

Greg Bogdan

Software Engineer, Blogger, Tech Enthusiast

I am a Software Engineer with over 7 years of experience in different domains(ERP, Financial Products and Alerting Systems). My main expertise is .NET, Java, Python and JavaScript. I like technical writing and have good experience in creating tutorials and how to technical articles. I am passionate about technology and I love what I do and I always intend to 100% fulfill the project which I am ...

下一篇文章

Using LINQ