Before I start this tutorial, a small disclaimer. This is my first ever tutorial and an attempt to return something back to the community which helped me learnt the necessary skills and concepts. Feedbacks are welcomes in case you find any kind of mistake / area of improvement in code / blog.
I was asked by someone to show off my learning skills (I like taking practise tasks). I didn't know C# at all and was asked to create a multithreading application with following requirements:
- Create a thread that will fill the list with 5 to 10 random integer items from 1 to 10000 every second.
- Create a thread that will fill the list with 3 to 8 random integer items from 1 to 10000 every half second.
- Create a thread that will remove the 20 to 30 biggest items from the list every 2 seconds.
- Create a dialog that will display a progress bar (with a value going from 0 to 60) and a cancel button.
- Create a thread that will increment the progress bar value by 1 every second and gracefully cancel everything at the end of the progress (i.e. 60)
- Handle the Cancel button on the dialog to gracefully cancel everything when the user clicks it.
- The application UI should display the list contents, updated every half second.
This is the snapshot of the application I made. Not a great looking one but it works. :)
Now lets see what a thread is. Thread is a task which shares the same memory space of the parent application. An application may have various sub-tasks which can perform different operations at parallel.
Now while executing the tasks at parallel, these threads might need to update the same data ( like in this case ). So we come across a situation when two threads modifies the same data block and if this is situation is not handled properly, the latter thread might end up removing the former thread modification. Hence, we need a mechanism which would prevent such cases.
In our application we would perform an exclusive lock to the data at the time of modification( write operation) so that the above mentioned condition can be avoided.
Beware, these situation ends up creating other issues such as deadlock ( where two threads wait for each other's resource forever ). However those issues are not covered in this tutorial.
Now lets come back to the problem and see how its solved.
At first lets create a Thread property class so that we use "DO NOT REPEAT YOURSELF" principle (Pragmatic Programmers).
class ThreadProperty : Object
{
public int min;
public int max;
public int sleepTimer;
public ThreadProperty()
{
min = 0;
max = 0;
sleepTimer = 0;
}
public void setThreadProperties(int min, int max, int sleepTimer)
{
this.min = min;
this.max = max;
this.sleepTimer = sleepTimer;
}
public void setTimer(int sleepTimer)
{
this.sleepTimer = sleepTimer;
}
};
Now lets do the designing part. As its not Windows form tutorial I would quickly skim over what I did. As long as you are aware of toolbox window in visual studio, it wouldn't be hard for you to create the following components.
Whats we need is
1) ListBox
2) Progressbar
3) Button (For Cancel and kill all the threads)
I did put my progress bar and button in a group box for docking purpose. However you may choose whatever suits you.
For simplycity, I would name these components so that the upcoming code is a lot easier.
Listbox - listUI
Cancel Button - bttnCancel
ProgressBar - componentProgressBar
Now we create the following members. To update the list ( in this case I used integer. You are welcome to extend this to use templates for data independence ).
public static List list;
public static Random randomNum = new Random( DateTime.Today.Millisecond );
public int minValue = 0;
public int maxValue = 0;
Thread modifier;
Thread modifierTwo;
Thread threadDelete;
Thread threadShowList;
Thread threadProgress;
Lets see each member variable declared above.
public static List
This is the list of the integers which will be modified by all the threads. You are welcome to use templates and make this list data independent.
public static Random randomNum = new Random(DateTime.Today.Millisecond);
Random number generator as specified in the task. Generating a random seeder in constructor.
public int minValue = 0;
public int maxValue = 0;
Keep track of min and max value. Now that one of the function we would do is to remove the biggest 20 - 30 values. Every time we perform the remove operation, we would need to iterate through all the list elements and delete the highest values. There is an alternate to this solution by keeping another stack of max values which would insert the value on the top of the stack if the pushed value is greater than current stack max value. Keep this stack sorted so that when we need to delete the values, all we would need to do is get the reference of the values and delete them.
Thread modifier;
Thread modifierTwo;
Thread threadDelete;
Thread threadShowList;
Thread threadProgress;
These are the threads which we would execute in order to modify the list of integers.
Now lets see our resource management functions in the application.
Lets take care of initializing these threads, executing them and releasing the threads after we are done with them. Before we see the code let me introduce the concept of foreground thread and background thread.
As per my understanding there are two types of thread execution. Background thread and foreground thread. If the thread is assigned as foreground thread, the application wouldn't exit till it stops. However all the background threads would exit once the foreground ones exit ( In this case the main form application is the foreground application and the member threads we created to manipulate the list are background threads). So in our case if we press the close button the application background threads would exit and the application will be closed.
Thats been said, lets start with thread initialization function.
private void InitializeThreads()
{
//..Initialize all the threads
ThreadProperty propInsertThread = new ThreadProperty();
ThreadProperty proInsertThread02 = new ThreadProperty();
ThreadProperty propDeleteThread = new ThreadProperty();
ThreadProperty propDisplayThread = new ThreadProperty();
ThreadProperty propProgressThread = new ThreadProperty();
modifier = new Thread(new ParameterizedThreadStart(this.AddToList));
modifier.IsBackground = true;
modifier.Name = "Insert Thread 1";
propInsertThread.setThreadProperties(1, 10000, 1000);
modifier.Start(propInsertThread);
modifierTwo = new Thread(new ParameterizedThreadStart(this.AddToList));
modifierTwo.IsBackground = true;
modifierTwo.Name = "Insert Thread 2";
proInsertThread02.setThreadProperties(1, 10000, 500);
modifierTwo.Start(proInsertThread02);
threadDelete = new Thread(new ParameterizedThreadStart(this.removeElements));
threadDelete.IsBackground = true;
threadDelete.Name = "Delete Thread";
propDeleteThread.setThreadProperties(20, 30, 10000);
threadDelete.Start(propDeleteThread);
threadShowList = new Thread(new ParameterizedThreadStart(this.readList));
threadShowList.IsBackground = true;
threadShowList.Name = "Show List Thread";
propDisplayThread.setTimer(500);
threadShowList.Start(propDisplayThread);
threadProgress = new Thread(new ParameterizedThreadStart(this.updateProgress));
propProgressThread.setTimer(100);
threadProgress.Start(propProgressThread.sleepTimer);
}
Lets take each line at the time and try to understand it.
ThreadProperty propInsertThread = new ThreadProperty();
ThreadProperty proInsertThread02 = new ThreadProperty();
ThreadProperty propDeleteThread = new ThreadProperty();
ThreadProperty propDisplayThread = new ThreadProperty();
ThreadProperty propProgressThread = new ThreadProperty();
We initialize the thread property of each thread we would create so that we can call the sleep functions on the basis of these property configuration.
modifier = new Thread(new ParameterizedThreadStart(this.AddToList));
modifier.IsBackground = true;
modifier.Name = "Insert Thread 1";
propInsertThread.setThreadProperties(1, 10000, 1000);
modifier.Start(propInsertThread);
We initialize the modifier thread as Parameterized thread. Its basically a delegate which takes the function as a parameter which it would executed once started ( In this case the thread would call our custom function AddToList() and insert the values to the list ). Then as explained above, we would like this thread to exit upon application close. Therefore we make this thread as background thread. Next line just assigns the name for clarity ( and more importantly debugging if something goes wrong). Then we go ahead and intialize the property. This thread would generate a random number from 1 to 10,000 when insert a new value and then would sleep for 1000 ms. The last line executes the thread. The parameter is the object of our TheadProperty class. You will soon see that our function takes specified parameter
Similarly we initialize another threads as listed in the function.
Now lets take a look at the function AddToList.
public void AddToList(object threadProperties)
{
ThreadProperty threadProperty = (ThreadProperty)threadProperties;
int num = randomNum.Next(threadProperty.min, threadProperty.max);
int sleepTimer = threadProperty.sleepTimer;
while (true)
{
lock (list)
{
list.Add(num);
Console.WriteLine( " Num of items in list : " + list.Count);
}
Thread.Sleep(sleepTimer);
}
}
Here goes the explaination. As the thread function start ( Eg.
We get the radom number between the threadProperty min and max and store it in the num variable. Now moving on, we create an infinite while loop as we want this thread to execute till the application ends / cancel button is pressed.
Now comes the interesting line of code.
lock (list)
{
list.Add(num);
Console.WriteLine( " Num of items in list : " + list.Count);
}
If you remember the lock mechanism as explained in the begining of the tutorial, this function takes the object and put a lock on it. This is a safe way to establish the fact that no other thread modifies the values of this list till this thread release the lock. We insert the value in the list. And then come out of statement which release the lock. ( You may ignore Console.Writeline statement as its for debuggin purpose. However if you are absolute beginner in C#, it outputs the message on debug console ).
Thread.Sleep(sleepTimer);
The thread goes in the sleep state as mentioned in the task.
The above mentioned function takes care of of two insert threads which will insert new values in the list.
Now lets take a look another thread functionality which takes care of deleting the data. We already saw this code in InitializeThread
threadDelete = new Thread(new ParameterizedThreadStart(this.removeElements));
threadDelete.IsBackground = true;
threadDelete.Name = "Delete Thread";
propDeleteThread.setThreadProperties(20, 30, 10000);
threadDelete.Start(propDeleteThread);
As explained this thread will executes removeElements function. The min and max values specified in ThreadProperty are the number of items this thread would remove.
Lets take a look how to do that.
public void removeElements(object threadProperties)
{
ThreadProperty threadProperty = (ThreadProperty)threadProperties;
int sleepTimer = threadProperty.sleepTimer;
while (true)
{
int num = randomNum.Next(threadProperty.min, threadProperty.max);
lock (list)
{
//..If there are less than 30 elements. Delete them anyway.
if (list.Count < num)
{
list.Clear();
}
else
{
List<int> sortedList = list;
sortedList.Sort();
for (int i = 0; i < num; i++)
{
int largestNum = sortedList[sortedList.Count - 1];
list.Remove(num);
sortedList.RemoveAt(sortedList.Count - 1);
Console.WriteLine("Num of items in list" + list.Count);
}
}
}
Thread.Sleep(sleepTimer);
}
}
Okay. The first few lines should be familiar as they are same as AddToList function. I will start explaination from
//..If there are less than 30 elements. Delete them anyway.
if (list.Count < num )
As the max number of elements needs to be deleted are 30. If the total elements in the list are less than the number the random generator generates, we don't have to even bother about greatest elements. Just get rid of all the numbers.
else
{
List
sortedList.Sort();
for (int i = 0; i < num; i++)
{
int largestNum = sortedList[sortedList.Count - 1];
list.Remove(num);
sortedList.RemoveAt(sortedList.Count - 1);
Console.WriteLine("Num of items in list" + list.Count);
}
}
Thread.Sleep(sleepTimer);
If the number of items in the list are greater than 30, then use to Sort function and remove the end elements of the list. After remove, release the lock and let the thread take some rest ( as per requirements ).
Now lets move forward to show the backstage operations to the user. We will dig in how Display thread reads the data and update the elements in the list box. There are two functions :
1) ReadList
2) SetListUI
public void ReadList(object threadProperties)
{
int sleepTimer = ((ThreadProperty)threadProperties).sleepTimer;
while (true)
{
this.SetListUI();
Thread.Sleep(sleepTimer);
}
}
private void SetListUI( )
{
if( this.listUI.InvokeRequired )
{
SetListUICallback d = new SetListUICallback(SetListUI);
this.Invoke( d );
}
else
{
lock( list )
{
lock (listUI)
{
listUI.Items.Clear();
this.lblnumOfElements.Text = list.Count.ToString();
foreach (int element in list)
{
//String elementInfo = "item numer " + list.IndexOf(element) + " : " + element.ToString();
listUI.Items.Add(element);
}
}
}
}
}
Read list uses the same logic of infinite while look and call SetListUI before it goes to sleep. Moving forward to SetListUI, we would see some new statements.
if( this.listUI.InvokeRequired )
{
SetListUICallback d = new SetListUICallback(SetListUI);
this.Invoke( d );
}
Lets take a look at "InvokeRequired" condition. In .Net when a method needs to be called by Thread 1 on the control which is created by some other thread ( Lets say Thread 2 ), we cannot call the function directly. We would need to make a delegate and call the function through that. So this if statement see if the indirect call needs to be made due to different threads and calls the function.
else
{
lock( list )
{
lock (listUI)
{
listUI.Items.Clear();
this.lblnumOfElements.Text = list.Count.ToString();
foreach (int element in list)
{
//String elementInfo = "item numer " + list.IndexOf(element) + " : " + element.ToString();
listUI.Items.Add(element);
}
}
}
}
Now we need to update our listbox. We clear the items, iterate through the list of integers modified by other threads and display it on listbox using listUI.Items.Add functionality.
Phew! Writing tutorial demands more effort than I thought it would. Anyways, We are almost done! The only left is progress bar and the cancel button. Lets finish this up. Here comes the progress bar code for update and display ( real piece of cake! ) :
public void updateProgress( object threadSleepTimer )
{
int sleepTimer = (int)threadSleepTimer;
while( true )
{
showProgress();
Thread.Sleep(sleepTimer);
}
}
private void showProgress()
{
if( this.componentProgressBar.InvokeRequired )
{
ShowProgressCallback progressCallback = new ShowProgressCallback(this.showProgress);
this.Invoke(progressCallback);
}
else
{
if (componentProgressBar.Value >= componentProgressBar.Maximum)
componentProgressBar.Value = componentProgressBar.Minimum;
this.componentProgressBar.Value += 1;
}
}
updateProgress does the same familiar stuff. Calls the show progress and sleep for a couple of milliseconds. Now showProgress is almost same as SetListUI as far as alternate thread method invoke is concerned. The only differences are following statements
if (componentProgressBar.Value >= componentProgressBar.Maximum)
componentProgressBar.Value = componentProgressBar.Minimum;
this.componentProgressBar.Value += 1;
If the value of the progress bar is greater than what we want ( in this case 60 ), reset it, else increment the progress value by one. Shouldn't be a problem to if you understood the other similar function's explanation.
Well that's about it friends. So here is my first ever tutorial. I have used
http://formatmysourcecode.blogspot.com/ for code format as the simple copy paste came out to be extremely ugly. In case there are some typos / some part of explanation missing, please leave a comment and I would try my best to keep it updated.
This was one of the simplest Hello World kind of programs I ever wrote and the reason for writing this tutorial was to find out the hidden challenges.
You may expect a slightly advanced code tutorial soon. Would love to see the feedback and comments.