Posted by: jaimalchohan on: May 13, 2008
Threading
If you don’t know what a process is, click Crtl + Shift + Esc to bring up the task manager, click on the processes tab and you’ll see all the processes running on your PC (You might need to uncheck “Show processes from all users” to see all of the processes).
A process is a programs container to which the operating system assigns security privileges, memory and processing priority (as well as a few other things). Open up Microsoft Word and you’ll see the WORD process pop up on the list.
In the Task Manager click on View, on the toolbar, select Columns, check Thread Count and click OK. You’ll see a new column displaying the number of threads contained within a process. The threads are what actually execute code, and most applications will have several threads running. For example Microsoft Word might have 1 thread for the window where you type another listening for events (clicking an icon, typing a character etc). It might also have some sleeping threads, threads waiting for something to occur, for example Word will auto-save your document every 5 minutes, As auto-saving doesn’t happen all the time, Word might put this into a Wait state (Sleep) until required.
Multi-threading allows Microsoft Word to provide you with a responsive experience, where a lot of processing occurs, but you would never tell from the speed of the application.
Threading in .NET allows you to create your own multi-threaded applications so your users can have a smoother user experience, and you can make better use of your processor.
Lets jump into an example.
public static class Program
{
public static void Main()
{
SimpleThreadingDemo simple = new SimpleThreadingDemo();
simple.FirstThread();
Console.ReadLine();
}
}
internal class SimpleThreadingDemo
{
public void FirstThread()
{
ThreadStart ts = new ThreadStart(DoWork);
Thread newThread = new Thread(ts);
newThread.Start();
Console.WriteLine(“First Thread”);
}
private void DoWork()
{
Console.WriteLine(“Second Thread”);
}
}
See that delegate. Arn’t you glad you read Part 1 now?
You’ll also notice as were doing 2 things at the same time, they’re running asynchronously! Although it’s not truly Asynchronous programming as DoWork() can’t return data to FirstThread().
Lets do something slightly more intresting
public static class Program
{
public static void Main()
{
SimpleThreadingDemo simple = new SimpleThreadingDemo();
simple.FirstThread();
Console.ReadLine();
}
}
internal class SimpleThreadingDemo
{
public void FirstThread()
{
ThreadStart ts = new ThreadStart(DoWork);
Thread newThread = new Thread(ts);
newThread.Start();
for (int i = 0; i < 10; i++)
{
Console.WriteLine(“First Thread: {0}”, DateTime.Now.Ticks);
Thread.Sleep(100);
}
}
private void DoWork()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(“Second Thread: {0}”, DateTime.Now.Ticks);
Thread.Sleep(100);
}
}
}
When you run this you’ll see that both threads are defiantly running at the same time, and if you have a fairly modern CPU (Dual/Quad Core or Hyper Threaded) you’ll see that some of the messages get written to the console screen at exactly the same time!
You’ll notice the Thread.Sleep method. This method pauses the thread for the specified number of seconds. What’s important to remember here is that it will only pause the current thread (the that thread which executes that line of code). You can’t call Sleep on other threads.
Parameters
What if we wanted to pass some data to the DoWork method? Use the ParameterizedThreadStart delegate.
public static class Program
{
public static void Main()
{
SimpleThreadingDemo simple = new SimpleThreadingDemo();
simple.FirstThread();
Console.ReadLine();
}
}
internal class SimpleThreadingDemo
{
public void FirstThread()
{
ParameterizedThreadStart ts = new ParameterizedThreadStart(DoWork);
Thread newThread = new Thread(ts);
newThread.Start(“This String Will be Cast Into an Object”);
for (int i = 0; i < 10; i++)
{
Console.WriteLine(“First Thread”);
Thread.Sleep(100);
}
}
private void DoWork(object myObject)
{
string s = (string)myObject;
for (int i = 0; i < 10; i++)
{
Console.WriteLine(s);
Thread.Sleep(100);
}
}
}
Hopefully you will have noticed above that I had to pass the parameter as an object. With ParameterizedThreadStart you can only pass 1 parameter and it has to be an object type. Personally I really dislike passing objects as parameters but there’s no other option here.
Locking
When running a muti-threaded application, special care must be taken when modifying shared data. For example imagine you had a counter which was incremented every time a method was called. Being multi-threaded it’s likely that the method will be called twice (or more) at exactly the same time, in which case your counter might not work as intended.
public static class Program
{
public static void Main()
{
SimpleThreadingDemo simple = new SimpleThreadingDemo();
simple.FirstThread();
Console.ReadLine();
}
}
internal class SimpleThreadingDemo
{
public void FirstThread()
{
Thread[] threads = new Thread[10];
SharedCounter shared = new SharedCounter()
for (int looper = 0; looper < 10; looper++)
{
Thread th = new Thread(new ThreadStart(shared.Increment));
threads[looper] = th;
th.Start();
}
for (int looper = 0; looper < 10; looper++)
{
threads[looper].Join();
}
Console.WriteLine(“Value is: ” + shared.counter);
}
}
internal class SharedCounter
{
public int counter;
public void Increment()
{
for (int i = 0; i < 100000; i++)
{
counter += 1;
}
Console.WriteLine(“Finished a loop”);
}
}
10 threads are created; each one executes the increment method on the same SharedCounter instance. This means that the counter variable is effectively shared between them
The above code created 10 threads and each thread calls a method which increments the counter 100,000 times. The result we expect is 100,000 x 10 = 1,000,000, but it’s not.
That’s because the counter variable is stored in memory somewhere, and every time a thread needs to increment it, it has to retrieve the value from memory, add 1, and save it again. If during this process another thread retrieves the value from memory, then it will overwrite what the previous thread saved.
Using the Join() method on the threads above tells the main thread (the thread which had to run the loop to create the 10 threads) to wait until each thread is finished. If we don’t include this then the value of counter will be displayed before all the threads have finished executing (try it).
How can we solve this? By using the Interlocked.Increment method.
internal class SharedCounter
{
public int counter;
public void Increment()
{
for (int i = 0; i < 100000; i++)
{
Interlocked.Increment(ref counter);
}
Console.WriteLine(“Finished a loop”);
}
}
When a thread attempts to increment counter, a lock will be created on counter variable, and all other threads will have to queue up and wait until it is unlocked. Now when we run the code, we get the desired result.
There’s a limitation in using Interlocked.Increment() in that it only works with integers. What if we need to modify a string, or any other object instead of just an int? In this situation we need to use a Synchronization (Sync) Lock.
internal class SharedCounter
{
public int counter;
public void Increment()
{
lock (this)
{
for (int i = 0; i < 100000; i++)
{
counter++;
}
}
Console.WriteLine(“Finished a loop”);
}
}
This time, as soon as a thread gets to the lock(this) keyword, the first thread creates a lock on the entire object (SharedCounter) until the end of the lock is reached. When the next thread reaches the lock keyword, it will wait until the object is unlocked.
Under the covers the lock keyword uses the Monitor class to perform its task, and if you need a greater amount of control, then the Monitor class allows you to specify timeouts for locks and allows you to reacquire a lock after you lose it. Its unlikely that you’ll ever need this amount of control in your day to day coding, but its good know.
internal class SharedCounter
{
public int counter;
public void Increment()
{
Monitor.Enter(this);
try
{
for (int i = 0; i < 100000; i++)
{
counter++;
}
}
finally
{
Monitor.Exit(this);
}
Console.WriteLine(“Finished a loop”);
}
}
If the code within the try… catch were to throw an exception then the lock would never be released, hence the try… catch.. finally… block.
Thread Pool
All the above code samples involve you creating your own threads. What if your multi-thread application had hundreds of users logged on at the same time? Then you’d be creating hundreds of threads and as you might imagine this would decrease the performance of your application as each thread requires processor time to execute. You’d probably have to create a class to limit the number of threads created, and if all threads are being used then queue up work so it can be executed once a thread becomes available. That’s what the ThreadPool does.
To use the ThreadPool you do the following
private void ThreadPool(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), 20);
}
It’s that easy.
The default maximum number of threads is 20, to change this you can add the following into your web.config.
IO threads as the name suggests are used for IO operations (e.g. reading/writing to disk) and generally you’ll have more IO threads than worker threads. There’s no magic formula for calculating the best number of Threads for your application, it’s a case of testing and tweaking to an individual applications needs.
There is one slight caveat when using the ThreadPool, all of the threads are created as background threads (the Thread.IsBackground property is set to true). What this means is that if the thread which created those background threads, the main thread, ends or aborts then the background threads will also be aborted and will never be executed. For example, if your web page completes all its processing and returns to the user before you’ve done the work on the ThreadPool thread. To avoid this it’s best to perform short operations on background threads and use normal threads ( foreground threads) for more time consuming or critical work.
That’s it for part 2 of Asynchronous Programming, in the final part I’ll combine Threading, Delegates and Events to show of the Asynchronous Programming model.
June 4, 2008 at 6:20 pm
good job,