What is multi-threading?
If you were to ask 100 programmers the question "What is a thread?", you'd leave with 100 different answers. To me a Thread is a tool that allows concurrent, independent processes to run parallel to one another, without affecting each other, and without having to wait for the other to complete before it can execute. It used to be that an application had but a single thread, which meant a process was made operate sequentially from beginning to end, with no variations. With the introduction of multi-threading you can run step 1 in its own thread, while running step 2 in a separate thread. While that may not always be a good idea, especially if step 1 requires results from step 3, so that was just an example of how multi-threading works.
Why multi-thread?
Multi-threading allows your application to run more efficiently, multi-threading affords you the opportunity to run resource intensive processes in the background, allowing your interface to remain usable. If you were to run your entire application in a single thread, you would notice at times that your user interface would, for all intents and purposes, be unusable, until the current process completed. Though there are numerous advantages to creating multi-threaded applications, there are also safety concerns. The main concern when creating multi-threaded applications is the possibility of multiple threads manipulating the same data, or space in memory, at the same time, this is known as a Race Condition.
Debugging a Race Condition can be difficult, as the condition can be sporadic and are often hid from the developer, and may not appear until the application is ran repeatedly. As long as you write thread-safe code you can avoid race conditions. Determining if code is thread-safe usually isnt an easy task, but there are several items that will reveal unsafe code, such as instantiating a class that contains static variables and indirectly accessing shared memory with the use of pointers or handles.
Limiting or avoiding the sharing of data across multiple threads is a way of ensuring you're code is thread-safe, but there are times when this is necessary. Say you have a large process running in the background, and you're wanting to keep the user updated on the progress of said process. Since the controls on the form are only accessible by the thread in which they were created, and sharing of data between threads is dangerous, for the lack of a better term, what do you do?
In this situation you would use a Delegate to marshal the call onto the thread that created the controls, using the Invoke Method. There are four methods that you can call from any thread and still have your code considered thread-safe:Now that we have a better understand of what a thread is, and what multi-threading is, lets look at how we would go about using a Delegate to marshal a call onto the UI thread with the Invoke Method. First we, as usual, need references to our Namespaces. The Namespaces we will be using are:
using System; using System.Text; using System.Threading; using System.ComponentModel; using System.Collections.ObjectModel;
Now we need our variables that will be referenced throughout our class. One of the variables is an ISynchronizeInvoke which allows us to synchronously or asynchronously execute our delegate. So here are the variables I use:
#region Class variables //instance of our delegate private static ProcessStatus _status; //our thread object private static Thread _thread; //our ISynchronizeInvoke object private static ISynchronizeInvoke _synch; //our list we'll be adding to private static Collection<string> _listItems = new Collection<string>(); #endregion
Next, we need a Delegate which we will use to marshal our call to the UI Thread, so we can display the progress of our process to the user on the UI:
//our delegate, which will be used to marshal our call //to the UI thread for updating the UI public delegate void ProcessStatus(string Message,int status);
Next, as with any class we need Constructors. A constructor is a member function with the same name as its class, and are used to create, and can initialize, objects of their class type. Constructors can be empty, the can accept no parameters and set variables to a desired state when the class is initialized, they can also accept parameters for initializing variables global to the class to a certain value:
#region Constructors
/// <summary>
/// Constructor using overloading
/// </summary>
/// <param name="syn">our ISynchronizeInvoke object</param>
/// <param name="notify">our ProcessStatus object</param>
public CrossThreadCommunication(ISynchronizeInvoke syn, ProcessStatus notify)
{
//set the _synch & _status
_synch = syn;
_status = notify;
}
/// <summary>
/// empty constructor
/// </summary>
public CrossThreadCommunication()
{
_synch = null;
_status = null;
}
#endregion
Next, we get to the heart of our class, the methods that do our work. The first method will initialize our Thread. In this method we use the IsBackground Property of the thread to tell it to run in the background, then we name the thread and start it:
#region StartProcess
public void StartProcess()
{
//we need to create a new thread for our process
_thread = new System.Threading.Thread(AddItemsToList);
//set the thread to run in the background
_thread.IsBackground = true;
//name our thread (optional)
_thread.Name = "Add List Items Thread";
//start our thread
_thread.Start();
}
#endregion
When you create a new Thread you need to pass it the name of the process that the thread will be executing. In this case we are passing it the AddItemsToList, which we use to add items to our Collection<string>. While we are adding to our collection, we also lock the collection so it cannot be accessed by any other threads, preventing a race condition:
#region AddItemsToList
/// <summary>
/// method for adding items to our list
/// </summary>
private static void AddItemsToList()
{
//create a loop to add items to the List<>
//for demonstration purposes
for (int i = 0; i <= 100; i++)
{
//lock the List<> so no other threads
//can access it while our thread is working
lock (_listItems)
//add items to the list
_listItems.Add("List Item #: " + _listItems.Count);
//send message to update the UI
UpdateStatus("Adding...", i);
//sleep for 1/2 a second to look like its actually doing something
Thread.Sleep(500);
}
}
#endregion
Since this is merely an example we aren't really doing anything big here, we are simply looping from 0 to 100 adding the counter object's value to our list. We then, on each iteration through the loop, use Sleep Method of the Thread Class to make it appear to be a larger process than it really is. You will notice that right before the Sleep method there is a call to a method named UpdateStatus. UpdateStatus is the method we are using to send our message to the UI Thread.
Here we create an object array that holds 2 items. You will also notice the method requires 2 parameters, msg and status, these parameters will be used to populate our object array. We then use the Invoke Method of the ISynchronizeInvoke Interface:
#region UpdateStatus
private static void UpdateStatus(string msg, string status)
{
//create our Object Array
object[] items = new object[2];
//populate our array with the parameters
//passed to our method
items[0] = msg;
items[1] = status;
//call the delegate
_synch.Invoke(_status, items);
}
#endregion
Using this we can update any UI component in the main thread without worrying if the call is being made from the proper thread. So as you can see, there is a thread-safe way to share data between threads. Though we aren't using data, we're simply updating a component on the UI with the status of our background thread. This process can now rune while leaving the UI in a usable state.
That is the end of the tutorial on Cross Thread Communication in C#, I hope you found this tutorial useful and informative. As I stated before, this is just the first in a series on multi-threading in C#, and what is available in the System.Threading Namespace.
Happy Coding





MultiQuote






|