.NET Multithreading Example Using ThreadPool Class

By:   |   Updated: 2019-03-26   |   Comments   |   Related: > Application Development


Problem

In my previous Application Development tips, among other, we've talked about multithreading in .NET using different techniques such as using the BackgroundWorker class, as well as the Thread class.  In this tip, we will be examining another multithreading technique in .NET, that is using the ThreadPool class.

Solution

The ThreadPool class in .NET, just like the Thread class, belongs to the System.Threading namespace, and allows to create and control a thread, set its priority, and get its status.

In this tip, we are going to write a .NET C# Console App, that will be performing the below:

  • Read a CSV file with 10.000 lines and copy it to a second csv file.
  • Along with the copy process, display the progress percentage.

The above operation will be implemented using multithreading via the ThreadPool class.

Sample CSV File

The below screenshot, illustrates the CSV file that we will copy into a new one via our .NET application.

Sample source file.

OK, now let's proceed with creating our .NET C# Console Application.

Creating the .NET C# Console App

Let's start a new "Console App" project in Visual Studio 2017, name it "ThreadPoolExample", and save it in the folder "c:\tmp\demos" (you can use any folder you like):

New .NET Console App for demonstrating the ThreadPool class.

Below, you can see the workspace in Visual Studio just right after the empty project is created.

Workspace in Visual Studio for our newly created .NET Console App.

Now, let's start adding some code.

The first thing to do is to add the proper namespaces, in order to allow us to make use of their classes.

Therefore, we are adding the System.Threading and System.IO namespaces:

//add the System.Threading namespace
using System.Threading;
 
//add the System.IO namespace
using System.IO;

The System.Threading namespace will allow us to make use of the ThreadPool class, and the System.IO namespace will let us use all the necessary classes for performing the file copy operation.

OK, now let's take a look at the full source code for this example and then analyze it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
//add the System.Threading namespace
using System.Threading;
 
//add the System.IO namespace
using System.IO;
 
 
namespace ThreadPoolExample
{
    class Program
    {
        //Object used for passing parameters to the CopyFileThreadPool method
        class ThreadParams
        {
            public string sourceFileName { get; set; }
            public string targetFileName { get; set; }
        }
 
 
        static void Main(string[] args)
        {
            //custom thread parameter object
            ThreadParams tParams = new ThreadParams();
 
            //get the file name from command prompt - user input
            tParams.sourceFileName = args[0].ToString();
            tParams.targetFileName = args[1].ToString();
 
            //queue the file copy thread for execution
            ThreadPool.QueueUserWorkItem(new WaitCallback(copyFileThreadPool), tParams);
 
            //we do not allow the main thread to terminate because it
            //will also terminate the file copy thread
            Console.ReadKey();
        }
 
        private static void copyFileThreadPool(object tParams)
        {
            //read the custom thread parameter object
            ThreadParams tParamsInner = tParams as ThreadParams;
            string sourceFileName = @tParamsInner.sourceFileName;
            string targetFileName = @tParamsInner.targetFileName;
 
 
            Console.WriteLine();
            Console.WriteLine("File Copy Operation using the ThreadPool Class");
            Console.WriteLine();
            Console.WriteLine("File to copy: " + @sourceFileName);
 
            //count file lines
            StreamReader sr = new StreamReader(@sourceFileName);
            float totalLines = File.ReadLines(@sourceFileName).Count();
            sr.Close();
 
            string line = "";
            float progress = 0;
 
            Console.WriteLine();
            Console.WriteLine("Copying file...");
 
            int count = 0;
 
            //start reading the file and copying it to the second file
            using (StreamReader reader = new StreamReader(@tParamsInner.sourceFileName))
            {
                StreamWriter writer = new StreamWriter(@targetFileName);
 
                while ((line = reader.ReadLine()) != null)
                {
                    ++count;
 
                    //avoid adding blank line in the end of file
                    if (count != totalLines)
                        writer.WriteLine(line);
                    else
                        writer.Write(line);
 
                    //progress report
                    progress = (count / totalLines) * 100;
                    Console.WriteLine("Progress: " + Math.Round(progress, 2).ToString() + "% (rows copied: " + count.ToString() + ")");
                }
 
                writer.Close();
            }
 
            Console.WriteLine();
            Console.WriteLine("Original File: " + @sourceFileName);
            Console.WriteLine("New File: " + @targetFileName);
            Console.WriteLine();
            Console.WriteLine("press any key to exit...");
        }
 
    }
}

Code Analysis

Let's analyze the above code, in order to fully understand it.

The program's code consists of two methods and an inner class.

The inner class is named "ThreadParams" and has two string fields, that is "sourceFileName" and "targetFileName". The purpose of this class, is to be used as an input parameter in the "copyFileThreadPool" method.

Now, let's analyze the two methods of our program:

  • copyFileThreadPool: This is the method that will be undertaking the file copy operation and which will run as a new thread. It consists of two main objects, that is a StreamReader that reads the original file, and a StreamWriter object, that is used for creating the new file based on the source. Moreover, the operation's progress is reported via this method. Another important thing to note about this method, is that it takes as an input parameter an object. This object, as you will see in the analysis of the method "Main", is actually the "ThreadParams" object.
  • Main: This is the main method of the program. It is actually the main thread. The first thing we do in this method, is to instantiate a new object of the "ThreadParams" class and populate its two fields "sourceFileName" and "targetFileName" based on the user's input. Then we set our ThreadPool and queue the "copyFileThreadPool" method for execution by actually creating a new thread and passing the "copyFileThreadPool" method as a delegate parameter. Moreover, this is the time where we pass as a parameter to the "copyFileThreadPool" method, the instantiated object of the "ThreadParams" class, that is the "tParams" object. Last but not least, we include a "Console.ReadKey();" command in order for our main thread, not to quit before the other thread completes its execution. Of course, if you enter a key before the file copy operation is completed, then the application will exit because the main thread will end its execution.

Running the Application

We run the application using the below command in the command prompt:

Running the ThreadPool example .NET Console App.

As you can see, the program runs and reports progress, and a few minutes later, the application completes the file copy operation and reports its final status.

Output of the ThreadPool example app.

Now, let's check the file that was created by executing our .NET Console App:

The generated file after running the program.

As you can see in the above screenshot, our multi-threaded .NET app, successfully copied the source file into a new one.

Conclusion

In this tip, we discussed about another multithreading technique in .NET, that is the ThreadPool class. We created a simple .NET C# Console App and in addition to the main thread, we have added to the ThreadPool queue, another thread that was used for undertaking a file copy operation.

By now, we have studied three different techniques for multithreading in .NET, that is using the BackgroundWorker class, the Thread class, and in this tip, the ThreadPool class.

In my next tip, we will discuss about parallelism in .NET and how we can easily launch parallel tasks, thus managing to develop a multithreading behavior.

Next Steps


sql server categories

sql server webinars

subscribe to mssqltips

sql server tutorials

sql server white papers

next tip



About the author
MSSQLTips author Artemakis Artemiou Artemakis Artemiou is a Senior SQL Server and Software Architect, Author, and a former Microsoft Data Platform MVP (2009-2018).

This author pledges the content of this article is based on professional experience and not AI generated.

View all my tips


Article Last Updated: 2019-03-26

Comments For This Article

















get free sql tips
agree to terms