.NET Multithreading Example Using Thread Class

By:   |   Comments (2)   |   Related: > Application Development


Problem

In one of my previous Application Development tips, we've talked about how we can implement multi-threading in .NET, using the BackgroundWorker class. For that tip we worked on a Windows Forms .NET application and wanted to have full control, so we used the BackgroundWorker class, because it allows a more managed way for working with multi-threading and GUI components.

In this tip, we continue our journey in the Application Development world, and more specifically in multi-threading, by checking out another option for multi-threading in .NET, that is the .NET Thread class. Even though, BackgroundWorker is usually preferred when working with GUI applications and multi-threading, in order to maintain a continuity among my tips on multi-threading, again I will be working with a Windows Forms .NET C# application in this tip's examples.

Solution

The Thread class in .NET, belongs to the System.Threading Namespace, and allows to create and control a thread, set its priority, and gets its status.

In this tip, we are going to perform the below:

  • Read a CSV file with 50.000 lines and display its contents in a TextBox control.
  • At the same time, using multi-threading, we will be displaying the progress of the above operation with a progress bar and a label with the percentage of completion value.

Sample CSV File

The below screenshot, illustrates the sample CSV file that we will be importing in the TextBox control via our application.

Sample CSV file to be imported into the .NET Application we will create in this tip.

OK, now let's proceed with the creation of the Windows Forms .NET C# Application.

Create Multithreaded Windows Forms Application using .NET C#

Let's start a new "Windows Forms App" project in Visual Studio 2017, name it "myTestApp", and save it in the folder "c:\temp\demos" (this is a similar procedure to one of my previous tips):

Starting a new Windows Forms App in Visual Studio 2017.

Right after we perform the above, our Windows Forms project opens and we are ready to work by adding controls on the form, along with their corresponding handling code.

Workspace in new Windows Forms App in Visual Studio 2017.

Let's resize the form and also perform the below:

  • Rename the form to "frmMain" and set its Text value to "Multi-Threaded Application"
  • Add a TextBox Control, resize it, set its "ReadOnly" property to "True" and name it "txtRetrievedData"
  • Add a Button control, resize it, name it "btnRetrieveData" and set its Text value to "Retrieve Data"
  • Add another Button control, resize it, name it "btnStop" and set its Text value to "Stop"
  • Add a ProgressBar control, resize it, name it "prgStatus" and set its Style to "Continuous"
  • Add a Label control, name it "lblStatus" and initialize its value to "0%"

So, after doing the above and build our solution by pressing F6, this is what we get:

After adding the GUI controls in our Windows Forms App in Visual Studio 2017.

Now, by pressing F7 we go into the code editor where at the top, after the "using" statements, we add the below two namespaces:

using System.Threading;
using System.IO;

By including the System.Threading namespace in our project, we will be able to use the Thread class.

Also, by including the System.IO namespace, we will be able to perform I/O operations such as reading and writing to files.

Now, let's see the entire code below and further discuss it:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.IO;
 
namespace myTestApp
{
    public partial class frmMain : Form
    {
        ThreadStart delegateRetrieveData;
        Thread mainThread;
 
        public frmMain()
        {
            InitializeComponent();
        }
 
        private void frmMain_Load(object sender, EventArgs e)
        {
 
        }
 
        private void btnRetrieveData_Click(object sender, EventArgs e)
        {
            //initializations
            txtRetrievedData.Clear();
            prgStatus.Value = 0;
 
            //the methods that will be executed by the main thread is "retrieveData"
            delegateRetrieveData= new ThreadStart(retrieveData);
            mainThread= new Thread(delegateRetrieveData);
 
            //start the main thread
            mainThread.Start();
 
        }
 
        private void retrieveData()
        {
            //read all lines in the csv file
            string[] lines = System.IO.File.ReadAllLines(@"C:\Demos\SampleFile.csv");
 
            //set the max value for the progress bar
            if (prgStatus.InvokeRequired)
            {
                Invoke(new MethodInvoker(
 
                    delegate
                    {
                        prgStatus.Maximum = lines.Length;
 
                    }));
            }
            else
            {
                prgStatus.Maximum = lines.Length;
            }
 
 
            //read lines and add them in the TextBox
            foreach (string line in lines)
            {
                //thread-safe call: append line in TextBox
                if (txtRetrievedData.InvokeRequired)
                {
                    Invoke(new MethodInvoker(
 
                        delegate
                        {
                            txtRetrievedData.AppendText(@line);
                        }));
                }
                else
                {
                    txtRetrievedData.AppendText(@line);
                }
 
 
                //thread-safe call: update progress bar
                if (prgStatus.InvokeRequired)
                {
                    Invoke(new MethodInvoker(
 
                        delegate
                        {
                            prgStatus.Invoke(new updatebar(this.UpdateBarProgress));
 
                        }));
                }
                else
                {
                    prgStatus.Invoke(new updatebar(this.UpdateBarProgress));
 
                }
            }
 
 
        }
 
        //delegate for calling the UpdateBarProgress method
        public delegate void updatebar();
 
        private void UpdateBarProgress()
        {
            if (prgStatus.Value < prgStatus.Maximum)
            {
                prgStatus.Value += 1;
 
                //thread-safe call: update lblStatus value
                if (lblStatus.InvokeRequired)
                {
                    Invoke(new MethodInvoker(
 
                        delegate
                        {
                            lblStatus.Text = (((float)prgStatus.Value / (float)prgStatus.Maximum) * 100).ToString("0.00") + " %";
 
                        }));
                }
                else
                {
                    lblStatus.Text = (((float)prgStatus.Value / (float)prgStatus.Maximum) * 100).ToString("0.00") + " %";
                }
            }
        }
 
        private void btnStop_Click(object sender, EventArgs e)
        {
            //abort main thread's execution
            mainThread.Abort();
        }
    }
}

Code Analysis of Windows .NET Application Multithreading

As you can see, at the class-level I created two objects:

ThreadStart delegateRetrieveData;
Thread mainThread;

The "delegateRetrieveData" object will be used for representing the method that will be executed in the main thread. As you can see in the btnRetrieveData_Click method's code, the method that will be executed in the main thread is "retrieveData()".

So, the btnRetrieveData_Click method is triggered when the user clicks on the "Retrieve Data" button and does the following:

  • Some initializations
  • Creates the "delegateRetrieveData" and "mainThread" objects
  • Starts the main thread

Now let's examine the retrieveDatamethod. This method is used by the main thread and does the actual job. Therefore, retrieveData does the following:

  • Reads all lines from the csv file.
  • With a thread-safe call, sets the maximum value for the progress bar, that is the total number of lines contained in the file.
  • With another thread-safe call, it adds the lines in the TextBox
  • With a third thread-safe call, it invokes the updatebar delegate in order to update the progress bar's value, as well as update the status label's value. It is critical to note at this point, that using a delegate is a must in this case (i.e. reporting progress), because Threads cannot just simply call methods like the program's primary Thread. Instead, they need a so-called function-pointer, and in this case, the updatebar delegate serves the Thread just like that. More specifically, the updatebar delegate, serves as a function pointer for the UpdateBarProgress() method, which undertakes the task of updating the progress bar and status label.

Last but not least, in the handling code for the btnStop_Click method, that is when the "Stop" button is clicked, I have added a single line of code:

mainThread.Abort();

For the above code, whenever the "Stop" button is clicked, it sends a signal to the main thread in order to abort its execution.

Let's Run the Multi-threaded Windows Forms Application

OK, now let's run the application by pressing F5 and take a few screenshots in order to better understand how all the above created a multi-threaded application with different GUI components, that are being updated using separate threads.

Windows Forms App execution. We can see that the progress bar and status label are being updated.

As you can see from the screenshots, while the application is loading the data into the TextBox, another thread undertakes the task of updating the progress bar's and the status label's values, without having the application freeze (this would be the case if we did not use multi-threading).

Windows Forms App execution. We can see that the progress bar and status label are being updated.
Windows Forms App execution. We can see that the progress bar and status label are being updated.
Windows Forms App execution. We can see that the progress bar and status label got their final update, since the process has been completed.

Conclusion

In this tip, we examined another way of implementing multi-threading in C#. To this end, we built a simple Windows Forms .NET C# application, where we developed the functionality of loading a CSV file into a TextBox and in parallel update a progress bar and a status label, all of that, using multi-threading, thus allowing the application not to freeze and to be fully responsive.

Multi-threading can become quite handy in many applications, however, if it is not properly handled and structured, you may risk your code to become significantly complex and high maintenance. Therefore, since there is more than one way of implementing multi-threading in .NET, each time you create a new program that requires multi-threading, you will also need to select the most appropriate multi-threading approach. That is why, in my next tips, we will deep dive even more into .NET multi-threading, in order to check additional ways of multi-threading.

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



Comments For This Article




Monday, February 25, 2019 - 1:23:27 PM - Artemakis Artemiou Back To Top (79119)

Hi Edul,

Thank you very much for your kind words. I really feel honored.  

My motto has always been to "keep things simple" and I'm really glad you find my articles useful and easy to understand.

Thanks again.

Best Regards,

Artemakis


Monday, February 25, 2019 - 9:25:35 AM - Edul Chikhliwala Back To Top (79117)

Very useful article. Artemakis Arteiou has a great talent of writing clear and concise instructions which help us understand more difficult concepts (like multi-threading). I always look forward towards reading his articles. Keep up the good work!















get free sql tips
agree to terms