An overview of the Task Parallel Library (TPL) in C#

The Task Parallel Library (TPL) is a collection of public types and APIs within the .NET framework that facilitates the integration of parallelism and concurrency into applications. Located in the System.Threading.Tasks namespace aims to streamline the development of efficient, scalable, and responsive software solutions.

TPL presents several advantages in the realm of multithreading programming.

Key Features of Task Parallel Library

  1. Task-based Programming Model: Rather than engaging directly with threads, TPL introduces the Task concept, which signifies an asynchronous operation. This approach simplifies management compared to conventional threads and provides a higher-level abstraction for concurrent execution.
  2. Thread Pool Management: Internally, TPL utilizes a thread pool to oversee the creation and reuse of threads, thereby enhancing performance and minimizing the overhead associated with manually generating new threads.
  3. Automatic Load Balancing: TPL automatically allocates tasks to available threads according to system resources, allowing developers to focus on coding without the need to manage threads manually.
  4. Parallel Class: The Parallel class within TPL offers a straightforward method for executing loops concurrently, distributing loop iterations across multiple processors.
  5. Task Continuations: TPL facilitates the chaining of tasks through continuations, enabling the specification of a task to execute following the completion of another. This feature simplifies the coordination of intricate asynchronous workflows.
  6. Exception Handling: TPL incorporates a comprehensive exception-handling framework, where exceptions raised by tasks are automatically captured and can be managed collectively using AggregateException.

Advantages of utilizing Task Parallel Library (TPL)

  1. Enhanced Efficiency: The reuse of thread pool threads significantly reduces the overhead associated with thread creation and destruction.
  2. Improved Scalability: The TPL effectively manages and schedules tasks across various threads, facilitating superior scalability and performance in applications that require concurrent processing.
  3. Streamlined Code: The TPL offers a more advanced abstraction for handling parallelism and concurrency, simplifying the process of writing and maintaining asynchronous code.

Example 1. A simple example of TPL is to run a task asynchronously.

using System.Windows;
using System.Threading.Tasks; // Added missing using directive

namespace TPL
{
    public class TPLExample_1
    {
        public static void RunSimpleTask()
        {
            // Create and run a Task
            Task task = Task.Run(() =>
            {
                MessageBox.Show("An example of a simple Task which is running in the background.");
            });

            // Wait for the task to complete
            task.Wait();

            MessageBox.Show("Simple Task example is completed now.");
        }
    }
}

Result of a simple task execution.

Task execution

Example 2. I will elucidate this concept using a practical example from TPL, suppose in the event that you are required to execute multiple independent tasks concurrently, it is essential to subsequently process the outcomes and address any errors that may arise. For example, envision a situation where data is extracted from multiple SQL SERVER tables, followed by the processing of that information, and finally, the integration of the outcomes.

I have utilized two tables for this purpose, as illustrated below.

CREATE TABLE EmployeeDepartment (
    DepartmentID INT IDENTITY(100,1) PRIMARY KEY,
    DepartmentName NVARCHAR(100)
);

CREATE TABLE EmployeeDetails (
    EmployeeID INT PRIMARY KEY,
    EmployeeName NVARCHAR(100),
    DepartmentID INT,
    FOREIGN KEY (DepartmentID) REFERENCES EmployeeDepartment(DepartmentID)
);

INSERT INTO EmployeeDepartment (DepartmentName) VALUES ('HR');
INSERT INTO EmployeeDepartment (DepartmentName) VALUES ('IT');
INSERT INTO EmployeeDepartment (DepartmentName) VALUES ('Finance');

INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (1, 'Sanjay Kumar', 101);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (2, 'Aman Gupta', 102);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (3, 'Mariusz Postol', 101);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (4, 'Atul Gupta', 100);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (5, 'Jaimin Shethiya', 101);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (6, 'Onkar Sharma', 102);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (7, 'Nikhil Patil', 101);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (8, 'Tuhin Paul', 101);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (9, 'Shiv Sharma', 100);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (10, 'Nitin', 101);

View of used tables

Table

Execution of the complex TPL Code

Step 1. I have developed a straightforward WPF user interface for this purpose, allowing you to select your preferred option.

=>MainWindow.xaml<=

<Window x:Class="TPL.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TPL"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Grid>
        <StackPanel Orientation="Vertical" Margin="20,50,0,0">
            <Button x:Name="SimpleTaskExample"
                    Content="Simple Task Example"
                    Height="40"
                    Click="SimpleTaskExample_Click"
                    Margin="20"/>
            <Button x:Name="ComplexTaskExample"
                    Content="Complex Task Example"
                    Height="40"
                    Click="ComplexTaskExample_Click"
                    Margin="20"/>
        </StackPanel>
    </Grid>
</Window>

=>MainWindow.xaml.cs<=

using System.Windows;

namespace TPL
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void SimpleTaskExample_Click(object sender, RoutedEventArgs e)
        {
            TPLExample_1.RunSimpleTask();
        }

        private async void ComplexTaskExample_Click(object sender, RoutedEventArgs e)
        {
            string returnResult = await TPLComplexExample_2.RunTPLComplexExample();
            MessageBox.Show(returnResult, "Result after execution");
        }
    }
}

=>UI View<=

UI View

Step 2. I have developed a class titled “TPLComplexExample_2.cs” to implement the code for the intricate TPL example presented below.

using System.Data.SqlClient;
using System.Windows;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public class TPLComplexExample_2
{
    private static string sqlConString = "Server=DESKTOP-JNM9BF1\\SANJAYSERVER;Database=demo;User Id=sa;Password=1234;"; // Change it with your own connection string

    public static async Task<string> RunTPLComplexExample()
    {
        string taskReturnresult = "";
        List<List<Dictionary<string, object>>> databaseResult = null;

        // Create a collection of data sources in the form of tables.
        var dataSources = new List<string> { "EmployeeDetails", "EmployeeDepartment" };

        // Commence the process of gathering data from each source, specifically the tables.
        var fetchTasks = dataSources.Select(source => Task.Run(() => ExtractingDataFromVariousTables(source))).ToArray();

        try
        {
            // Please wait for the conclusion of all retrieval tasks.
            await Task.WhenAll(fetchTasks)
                .ContinueWith(async InformationResults =>
                {
                    // Combine the results that have been obtained.
                    if (InformationResults.IsCompletedSuccessfully)
                    {
                        databaseResult = InformationResults.Result.ToList();
                    }
                    else if (InformationResults.IsFaulted)
                    {
                        // Pass the error to the next continuation by wrapping it in Task.FromException.
                        taskReturnresult = InformationResults.Exception?.GetBaseException().Message 
                            ?? "An unidentified error occurred while retrieving data.";
                    }
                })
                .ContinueWith(async processingDataResults =>
                {
                    if (processingDataResults.IsCompletedSuccessfully)
                    {
                        taskReturnresult = await ShowMergingResultsOfDataFromDifferentTables(databaseResult);
                    }
                    else if (processingDataResults.IsFaulted)
                    {
                        taskReturnresult = processingDataResults.Exception?.GetBaseException().Message 
                            ?? "An unidentified error occurred while merging data.";
                    }
                });
        }
        catch (Exception ex)
        {
            // Address any exceptions that may occur.
            MessageBox.Show("An error occurred during execution of the program: " + ex.Message);
        }

        return taskReturnresult;
    }

    // Retrieve data from a designated table utilizing ADO.NET.
    private static async Task<List<Dictionary<string, object>>> ExtractingDataFromVariousTables(string tableName)
    {
        var data = new List<Dictionary<string, object>>();

        try
        {
            using (SqlConnection connection = new SqlConnection(sqlConString))
            {
                string query = $"SELECT * FROM {tableName}";
                SqlCommand command = new SqlCommand(query, connection);

                connection.Open();
                using (SqlDataReader reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var row = new Dictionary<string, object>();
                        for (int i = 0; i < reader.FieldCount; i++)
                        {
                            row[reader.GetName(i)] = reader.GetValue(i);
                        }
                        data.Add(row);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            return null;
        }

        return data;
    }

    // Analyze the consolidated outcomes by integrating EmployeeDetails and EmployeeDepartment information.
    private static async Task<string> ShowMergingResultsOfDataFromDifferentTables(List<List<Dictionary<string, object>>> results)
    {
        try
        {
            if (results == null)
            {
                return "Error occurred in fetching information from database.";
            }

            var employees = results[0];
            var departments = results[1];

            // Combine employees with their corresponding departments.
            var mergedData = from emp in employees
                             join dept in departments
                             on emp["DepartmentID"] equals dept["DepartmentID"]
                             select new
                             {
                                 EmployeeName = emp["EmployeeName"],
                                 DepartmentName = dept["DepartmentName"]
                             };

            // Delivering a concatenated string of results.
            string resultString = string.Join(", ", mergedData.Select(m => $"{m.EmployeeName} - {m.DepartmentName}"));

            return resultString;
        }
        catch (Exception ex)
        {
            return "Error occurred in fetching information from database.";
        }
    }
}

Step 3. Run the program to obtain the outcome.

Run the program

Output Result

(I) Upon successfully obtaining the results.

Sanjay Kumar - IT, Aman Gupta - Finance, Mariusz Postol - IT, Atul Gupta - HR, Jaimin Shethiya - IT, Onkar Sharma - Finance, Nikhil Patil - IT, Tuhin Paul - IT, Shiv Sharma - HR, Nitin - IT

Output

(II) Upon encountering an error during the program's execution.

Error


Similar Articles