By: Artemakis Artemiou | Updated: 2019-02-25 | 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.
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):
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.
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:
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, theupdatebar
delegate serves the Thread just like that. More specifically, theupdatebar
delegate, serves as a function pointer for theUpdateBarProgress()
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.
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).
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
- Check out my tip: Learn how to build a multithreading .NET Application to work with SQL Server
- Check out my tip: How to Get Started with SQL Server and .NET
- Check out my tip: Querying SQL Server Tables from .NET
- Check out my tip: Working with SQL Server Stored Procedures and .NET
- Check out my tip: Working with SQL Server Functions and .NET
- Check the MS Docs article: Thread Class
- Check the MS Docs article: ThreadStart Delegate
- Check the MS Docs article: Delegates (C# Programming Guide)
About the author
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-02-25