Friday, August 22, 2008

Threading with .NET ThreadPool Part 4

Suppose you've been given the task to write a function that copies the contents of one folder to another. So you set off on your merry way and come up with something like the following.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace ThreadPoolPart4
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
// Get the Folder names, copy contents from one to the other
FolderBrowserDialog fb = new FolderBrowserDialog();
fb.ShowDialog();
string src = fb.SelectedPath;
fb.ShowDialog();
string dst = fb.SelectedPath;
// no error checking on the names, this is an example only

if (dst != src)
{
if (!Directory.Exists(dst))
Directory.CreateDirectory(dst);
RecurseCopyFolder(new DirectoryInfo(src), new DirectoryInfo(dst), MyCopyCallback);
}
}
private void RecurseCopyFolder(DirectoryInfo src, DirectoryInfo dst, CopyCallbackDelegate cb)
{
bool cancelled = false;
CopyArgs ca;
try
{
foreach (FileInfo fi in src.GetFiles())
{
string newfile = Path.Combine(dst.FullName, fi.Name);

ca = new CopyArgs() { IsDir = false, CurrentObject = newfile };
cb(ref cancelled, ca);
if (cancelled)
break;
if (!ca.Skip)
{
if (Directory.Exists(ca.CurrentObject))
Directory.Delete(ca.CurrentObject);
fi.CopyTo(ca.CurrentObject, true);
}

}
if (!cancelled)
foreach (DirectoryInfo subsrc in src.GetDirectories())
{
ca = new CopyArgs() { IsDir = true, CurrentObject = Path.Combine(dst.FullName, subsrc.Name) };
cb(ref cancelled, ca);

if (cancelled)
break;
if (ca.Skip)
continue;
DirectoryInfo subdst;
if (!Directory.Exists(ca.CurrentObject))
subdst = dst.CreateSubdirectory(subsrc.Name);
else
subdst = new DirectoryInfo(ca.CurrentObject);

RecurseCopyFolder(subsrc, subdst, cb);
}
}
catch (Exception e)
{
throw e;
}
}

public class CopyArgs : EventArgs
{
public bool IsDir;
public bool Skip;
public string CurrentObject;
}

private delegate void CopyCallbackDelegate(ref bool Cancel, CopyArgs args);
private void MyCopyCallback(ref bool Cancel, CopyArgs args)
{
if (args.IsDir)
label1.Text = args.CurrentObject;
else
label2.Text = args.CurrentObject;
}

}
}


But when you test this on a somewhat larger folder, your application appears to hang. What gives? Well, it's very common for programmers to hang up their GUI by waiting, sleeping, reading the network, or in this case, doing intensive file I/O operations on the GUI thread. It's been said hundreds if not thousands of times on the forums, "don't Sleep() in the GUI thread". Because, if your GUI gets stuck in a Sleep() or anywhere else, it does not get a chance to pump its message loop. A GUI that does not service its message queue looks like it is dead. That's why sending your GUI down a recursive file copy excursion like we just did is a really bad idea.

That is also why threading has become a best practice for C# application development. And today, we take another look at the System.Threading.ThreadPool. In my previous articles on the ThreadPool ("Threading with .NET ThreadPool, Part 1, Part 2 and Part 3"), I spent the entire time talking about ThreadPool.QueueUserWorkItem(). But, there are other ways within your power to put the ThreadPool to work for you. Specifically, I am talking about the delegate.BeginInvoke() call. Let us see how it is used.

First off, to solve our dead looking GUI application (if your's doesn't look dead, you didn't try copying a big enough folder), we need to put the "heavy lifting" into a thread. All the hard work occurs in RecurseCopyFolder(), so we will put that call into a thread using BeginInvoke(). We do it by declaring a delegate for the function, then calling the delegate's BeginInvoke() like so...


private delegate void RecurseCopyFolderDelegate(DirectoryInfo src, DirectoryInfo dst, CopyCallbackDelegate cb);
private void RecurseCopyFolder(DirectoryInfo src, DirectoryInfo dst, CopyCallbackDelegate cb)
{ //...
}


You should be familiar with delegates, but if not, just note how the delegate call signature is exactly the same as the function it will delegate for. I name them similarly for the sake of self-documenting code.

Next, we create an instance of the delegate. I create a class scope variable to hold the delegate instance because I need it later. I also create an IAsyncResult variable because I will be needing that, too when the thread completes.


RecurseCopyTreeDelegate dlgt;
IAsyncResult asyncResult;


Now, we instantiate the delegate and get a new thread started with BeginInvoke(). Notice that BeginInvoke() takes the same parameters as the original function (plus two, we'll come back to those later). The "delegate" is implemented by the compiler, so the compiler also does us the favor of letting us call the BeginInvoke() almost like we would call the delegate itself.


// RecurseCopyFolder(new DirectoryInfo(src), new DirectoryInfo(dst), MyCopyCallback);
dlgt = new RecurseCopyFolderDelegate(RecurseCopyFolder);
asyncResult = dlgt.BeginInvoke(new DirectoryInfo(src), new DirectoryInfo(dst), MyCopyCallback, null, null);


We are almost ready to run, but notice we are using a callback function to update our GUI. You never update the GUI directly from a different thread, its not thread-safe. So we will add the code to make the callback check the form's InvokeRequired property and Invoke() the call if necessary. It is common to have one function check the InvokeRequired and call itself again with Invoke(), so I've modified MyCopyCallback() to do just that.


private delegate void CopyCallbackDelegate(ref bool Cancel, CopyArgs args);
private void MyCopyCallback(ref bool Cancel, CopyArgs args)
{
if (this.InvokeRequired)
{
this.Invoke(new CopyCallbackDelegate(MyCopyCallback), new object[] { Cancel, args });
}
else
{
if (args.IsDir)
label1.Text = args.CurrentObject;
else
label2.Text = args.CurrentObject;
}
}


There's still one more requirement for using BeginInvoke(). When using the "delegate" BeginInvoke(), you are required to call the delegate's EndInvoke() when the thread completes. So, in order to know when the function completes, you can use a timer (remember to enable it) and check the asyncResult variable...


private void timer1_Tick(object sender, EventArgs e)
{
if(asyncResult != null)
if (asyncResult.IsCompleted)
{
dlgt.EndInvoke(asyncResult);
asyncResult = null;
timer1.Enabled = false;
}
}


But, even better than a timer would be to use those two nice little parameters at the end of BeginInvoke(). They are the "Thread Completion Callback" and the "User Data Object". You supply a callback that will get called when the thread completes. It is called with the user data object passed in inside of the IAsyncResult parameter. We normally pass the delegate itself as the user data object so that we can use it to call the EndInvoke() with. This allows us to eliminate those class scope variables we added earlier (though I don't, I'll leave it as an exercise). So we still have to write a completion routine.


public void RecurseCopyDone(IAsyncResult result)
{
if (this.InvokeRequired)
{
this.Invoke(new AsyncCallback(RecurseCopyDone), new object[] { result });
}
else
{
RecurseCopyFolderDelegate d = result.AsyncState as RecurseCopyFolderDelegate;
d.EndInvoke(result);
label1.Text = "Done";
label2.Text = "Done";
}
}


The Invoke() above is not necessary to call the delegate's EndInvoke(), but it is needed to update my labels, so I opt to keep all the code together. EndInvoke() can be run from either the GUI thread or the callback thread.

So, there you have it. The complete source that follows of the Form1.cs and the Form1.Designer.cs has an additional button allowing us to cancel the operation. Copying files the way I am doing it cannot be interrupted, so cancellation occurs between files. The final version uses the IAsyncCallback instead of a timer. I hope this has been of use to you. Here's the code...

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace ThreadPoolPart4
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private bool CancelCopyFlag = false;
private IAsyncResult asyncResult = null;
private RecurseCopyFolderDelegate dlgt;
private void button1_Click(object sender, EventArgs e)
{
CancelCopyFlag = false;

// Get the Folder names, copy contents from one to the other
FolderBrowserDialog fb = new FolderBrowserDialog();
fb.ShowDialog();
string src = fb.SelectedPath;
fb.ShowDialog();
string dst = fb.SelectedPath;
// no error checking on the names, this is an example only

if (dst != src)
{
if (!Directory.Exists(dst))
Directory.CreateDirectory(dst);
// RecurseCopyFolder(new DirectoryInfo(src), new DirectoryInfo(dst), MyCopyCallback);
dlgt = new RecurseCopyFolderDelegate(RecurseCopyFolder);
asyncResult = dlgt.BeginInvoke(new DirectoryInfo(src), new DirectoryInfo(dst), MyCopyCallback, new AsyncCallback(RecurseCopyDone), dlgt);
}
}
private delegate void RecurseCopyFolderDelegate(DirectoryInfo src, DirectoryInfo dst, CopyCallbackDelegate cb);
private void RecurseCopyFolder(DirectoryInfo src, DirectoryInfo dst, CopyCallbackDelegate cb)
{
bool cancelled = false;
CopyArgs ca;
try
{
foreach (FileInfo fi in src.GetFiles())
{
string newfile = Path.Combine(dst.FullName, fi.Name);

ca = new CopyArgs() { IsDir = false, CurrentObject = newfile };
cb(ref cancelled, ca);
if (cancelled)
break;
if (!ca.Skip)
{
if (Directory.Exists(ca.CurrentObject))
Directory.Delete(ca.CurrentObject);
fi.CopyTo(ca.CurrentObject, true);
}

}
if (!cancelled)
foreach (DirectoryInfo subsrc in src.GetDirectories())
{
ca = new CopyArgs() { IsDir = true, CurrentObject = Path.Combine(dst.FullName, subsrc.Name) };
cb(ref cancelled, ca);

if (cancelled)
break;
if (ca.Skip)
continue;
DirectoryInfo subdst;
if (!Directory.Exists(ca.CurrentObject))
subdst = dst.CreateSubdirectory(subsrc.Name);
else
subdst = new DirectoryInfo(ca.CurrentObject);

RecurseCopyFolder(subsrc, subdst, cb);
}
}
catch (Exception e)
{
throw e;
}
}

public void RecurseCopyDone(IAsyncResult result)
{
if (this.InvokeRequired)
{
this.Invoke(new AsyncCallback(RecurseCopyDone), new object[] { result });
}
else
{
RecurseCopyFolderDelegate d = result.AsyncState as RecurseCopyFolderDelegate;
d.EndInvoke(result);
label1.Text = "Done";
label2.Text = "Done";
}
}

public class CopyArgs : EventArgs
{
public bool IsDir;
public bool Skip;
public string CurrentObject;
}

private delegate void CopyCallbackDelegate(ref bool Cancel, CopyArgs args);
private void MyCopyCallback(ref bool Cancel, CopyArgs args)
{
if (this.InvokeRequired)
{
this.Invoke(new CopyCallbackDelegate(MyCopyCallback), new object[] { Cancel, args });
}
else
{
if (args.IsDir)
label1.Text = args.CurrentObject;
else
label2.Text = args.CurrentObject;
Cancel = CancelCopyFlag;
if (Cancel)
{
label1.Text = "Cancelled";
label2.Text = "Cancelled";
}
}
}

private void button2_Click(object sender, EventArgs e)
{
CancelCopyFlag = true;
}
}
}


Form1.Designer.cs

namespace ThreadPoolPart4
{
partial class Form1
{
private System.ComponentModel.IContainer components = null;

protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.button1 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(18, 17);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(22, 71);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 1;
this.label1.Text = "label1";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(22, 113);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(35, 13);
this.label2.TabIndex = 2;
this.label2.Text = "label2";
//
// button2
//
this.button2.Location = new System.Drawing.Point(169, 22);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 3;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.button2);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();

}

#endregion

private System.Windows.Forms.Button button1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button button2;
}
}

Monday, August 18, 2008

Generics Fast

You are trying to come up to speed in C# fast. Then all of a sudden, you come across the C# Generic Type and wonder how do I master that.

Well, I've got some good news for you. You are in good company. You thought I was going to say I'll help you master generics. No, that's beyond a simple blog like mine. But, maybe I can help you become proficient. We just have to get the main learning hurdles out of the way.

Hurdle #1 - Rationale
Generic Types were added to C# in version 2.0. Now, I am not going to go into any detail on the history of C# or generics. I just mention this because the C# team decided they had a serious enough short coming in 1.1 to add this major feature. That is, many, many programmers were opting to ditch type-safety and decent performance in order to write more versatile data structures. How's that? Well, folks (MS included) were writing things like List and Queue and Stack to take "objects". Then their particular data structure could work on anything, and that would maximize code reuse (and likewise productivity).

The problem is, though re-use is greatly increased, productivity may be closer to a "wash" than a gain. That's because in implementing a "generic" structure that takes only "object" types, type-safety is lost. With lost type-safety, there's an increase in coding errors and run-time bugs and thus more time is spent testing and fixing the code. Okay, reusability is a win, productivity is a tie maybe, and performance is a what? Well performance is a loser. To use such a data structure with integers will require the compiler to "box" the type to make it an object. For reference types, the compiler spends extra time with cast operations. One article states the performance of the object data structure is about 200% slower than if it had been implemented as a specific value type.

Generic types solve the performance issue while giving the programmer back the type safety. And, clearly generic types provide for substantial code reuse.

Hurdle #2 - How to Use a Generic Type
Alright. It's good to know why, but it's better to know how. Let's start by using a generic type defined by the framework. Let's start with the List.


// instantiate a List of Strings
List strList = new List();
strList.Add("One");
strList.Add("Two");
foreach(string s in strList)
Console.WriteLine(s);


Rather than cut and paste this code into your IDE, go ahead and type it out. Intellisense will help you. In declaring your "strList" as a "List", everything about the list is now "string" oriented. The constructor, the Add(), the enumeration, etc. It's as if a wholesale find and replace has be done on the type place holder. Maybe you saw <T> in the intellisense, T is the place holder. More precisely, T is the "generic parameter" to the List<> type. And though, a macro like replacement is done, it's very different from the C++ Template where the compiler does the replacement. In C#, generics are implemented in the CLR, so its the JIT that is doing the final replacement. It will be good to keep this in mind when you "type-cast" your way around compiler errors only to have your code throw an exception at run-time on a bad conversion.

Hurdle #3 - How to Write a Generic Type
Which brings us to writing our own generic types. This next bit of example code makes use of several applications of the generic type syntax.


public class GenericClass
{
private T genProp; // a generic field
public T GenProp { // a generic property
get { return genProp; }
set { genProp = value; }
}
public GenericClass() // a generic constructor
{
}
public T GenMethod() // a generic method
{
return genProp;
}
public string GenMethodT(T val) // you get the picture
{
return val.ToString();
}
// and so on...
}
// ...
GenericClass gc = new GenericClass();
Console.WriteLine(gc.GenMethodT(5));


You can see that the generic parameter T is all over the place and each occurance, if you can imagine, will be filled in with whatever type you end up instantiating this class with. It's behavior will be approriate for whatever "generic argument" you pass.

So, we've got the "generic type", the "generic parameter" and the "generic argument". What are each of these? In the example above, the "generic type" is "GenericClass<T>", the "generic parameter" is "T", and the "generic argument" is "int". There can be multiple generic parameters, and the name of the variable is arbitrary (within naming rule limits, of course). To specify multiple parameters, simply separate them with commas.


Dictionary< int, string> dict = new Dictionary< int, string>();
dict.Add(1,"One");
dict.Add(2,"Two"); // and so on...

//...
public class MyNextGeneric
{
public T myMethod(U arg)
{
//...
}
}


Hurdle #4 - Using Generic Constraints
You are now well on your way to writing generic classes and methods. It won't take long before you start seeing some interesting compiler errors.


public bool GenMethodCompare(T val1, T val2)
{
return (val1 == val2) ? true : false;
}


This bit of code produces the error "Operator '==' cannot be applied to operands of type 'T' and 'T'". You may ask why?! That might be because you are thinking like C++ instead of C#. In C++, maybe you have instantiated the class with "int" which is very easy to compare. The C++ compiler can make the final call. But with C#, the JIT will make the final call, and though the compiler knows what generic arguments you are passing, it does not know what might be passed by an external assembly. The C# team could probably have figured something out, but with deadlines looming, who knows. Regardless, it's situations like these for which "Generic Constraints" were invented.

There are three kinds of Generic Constraint, "Derivation Constraints", "Construction Contraints", and "Reference/Value Type Constraints".

In the last example, the compiler "could" compile the generic method if instead of "==", we used object.Compare() and if the compiler could just be assured that any generic argument supplied for T will be an IComparable type object. This would be a "Derivation Constraint". Notice in this next example that I use a generic IComparable interface as the constraint. Any class or interface can be used as a constraint, even other generic parameters.


public MyClass where T : IComparable
{
public bool GenMethodCompare(T val1, T val2)
{
return (val1.Equals(val2)) ? true : false;
}
}


All the generic constraints use the syntax "where T : constraint, constraint" at the end of the type declaration (after any base class and/or interfaces) and before the open curly brace. If there are multiple generic parameters that each need constraints, the syntax would be


public class GClass : SomeBaseClass, ISomeInterface
where T : IComparable
where U : IEnumerable
{ ... }


"Construction Constraints" are used when your generic class must constuct objects of type T. You may get the error "Cannot create an instance of the variable type 'T' because it does not have the new() constraint". This is very self explanatory message in terms of what you've got to do. But, it occurs because your code is trying to instantiate an object of type "T". Many types are implemented which do not have default (i.e., parameter-less) constructors. Without the "new()" constraint, the compiler will not allow code that instantiates a generic parameter to compile. You can combine new() with other constraints by separating with a comma and placing it after the others.


public class GClass where T : IComparable, new()
{
public T factory() { return new T(); }
}


"Reference/Value Type Constraints" tell the compiler that only reference types or only value types are allowed as the generic argument. "class" indicates a reference type constraint, while "struct" indicates a value type constraint. Notice that it doesn't make any sense to combine the "struct" generic constraint with the "Derivation Constaints", they are incompatible. It doesn't make sense to combine the "class" generic constraint either, it is redundant.


public class RTypeClass where T : class { ... }
public class VTypeClass where T : struct { ... }


Finish Line
That's about all I can cover in an evening. You should have everything you need to start using and creating your own generic types. My article "Thread Synchronized Queing" is reasonable working example of a generic Queue class if you think that would be of any value to you. Just don't try writing anything too complex right away, I wouldn't want you to hurt yourself.

Useful Resources
An Introduction to C# Generics
Generics (C#)
Using C# Generics on Static Classes - Wes' Puzzling Blog
C# Frequently Asked Quetions - How do C# generics compare to C++ templates?

Tuesday, August 12, 2008

Sleep

When I first started programming, I thought "Sleep" to be one of the most useless calls I could make. No, I'm not talking about personal habits, I'm talking about the Sleep() function.


using System.Threading;
Thread.Sleep(int milliSeconds)

We find this little "do nothing" function in C, C++, C#, VB, VB.Net, WScipt, and similar functions in other languages. And, I have since come to understand how useful and confusing "doing nothing" can be. So, let's take a look at it; I'll be focusing on the C# Sleep() function.

Sleep() simply suspends the execution of your thread for a specific period of time. It relinquishes the remainder of its time-slice and becomes "unrunnable" for a period of time. If the time period is zero, it relinquish its time-slice only if another thread is ready and waiting to run. If there are no waiting threads, and the time span is zero, the calling thread remains ready to run and therefore returns from Sleep() immediately.

So, Sleep() is useful for delaying a thread, or for getting a thread out of the way of other threads. Let's say a user enters a password incorrectly, a delay at this point is useful to help dissuade a rapid fire brute force attack on your password input algorithm. Keep in mind that Sleep() also takes away the thread's responsiveness. So, delaying operations should only be implemented with Sleep() if you can tolerate or mitigate the total lack of responsiveness from the sleeping thread.

Sleep() is also useful for making a thread behave nicely by not hogging the CPU. The Operating System will preempt threads for you and let other threads run. But you may still see your thread taking up way too much processor resources and not allowing other threads enough time to run. A Sleep() for some very short interval could be useful in improving overall application responsiveness. Here again, calling Sleep(0) on a thread that is very busy processing messages is about useless because each call to the message queue already allows other waiting threads to run.

You can use Sleep() for very simple timed delays. A pattern where there is a producer and several consumers might use a Sleep() to throttle the producer to let the consumers keep up or catch up. That is, the producer might fill a queue and then stop and Sleep() when the queue gets too full. The amount of time the producer thread sleeps determine how much the consumers have a chance to process. But even so, a more robust implementation would have the producer "wait" for space available on the queue or even the queue reaching a "low water mark", rather than blindly sleeping.

The single most often misuse of Sleep() in C# is when a thread that has created a window then calls Sleep(). Threads that create windows, whether directly or indirectly, must remain awake to process messages. When a thread sleeps, it cannot process those messages and the application appears to hang. The C/C++ documentation for SleepEx() and Sleep() in fact warn that should a thread sleep indefinitely, when it should be processing messages, then the system will deadlock because message broadcasts are sent to all windows in the system.

We are not discussing C/C++ and we certainly are not going to sleep indefinitely. So, C# should keep us out of such a catastrophic situation, but the C# programmer must still avoid putting "GUI" threads to sleep to avoid hanging up his application. The C# documentation for Sleep() simply says "This method does not perform standard COM and SendMessage pumping". This hardly suggests how ugly the application behavior can be if you use Sleep() on your GUI thread. In short, just keep your Sleep() off of the GUI threads, and keep your COM and DDE calls (unless absolutely sure there is no GUI involved) on the GUI threads.

What are the alternatives to calling Sleep() on a GUI thread? First, and foremost, your best alternative is a good architecture starting out. GUI threads never "need" to sleep because they should normally be waiting for messages. If a GUI thread is so busy that it can't service the messages as they arrive, the GUI should offload the work to a separate thread. But, if your GUI is so busy, it's probably not going to sleep anyway. If a GUI needs to wait for something to happen that won't occur on its message/event loop, it should delegate that wait to a thread as well, and then use state variables to modify its behavior until the desired event occurs.

For example, reading from a network could wait for a very long time. A GUI would act like it's hung if it got stuck at a network read for any length of time. The same goes for reading streams. Often a stream may resolve to a network resource. Or, if you are reading a lot of data from a file, the application will similarly appear hung. By delegating the operation to a separate thread, the GUI is free to respond to other events. Its tempting at this point for the GUI to Sleep(), but don't do it. Instead, set a state variable that indicates an operation is in progress and prevent your other dependant GUI operations from proceeding until the operation finishes. Better yet, try to eliminate the GUI dependancy on the completion of the read altogether. Architect your application with threads well chosen for the task, especially if you must wait for various kinds of system events.

Another alternative to Sleep() is a "Timer" function (see my articles on timers). With a timer function, a thread simply schedules a time when it will get back to the item its waiting for. In the meantime, the GUI continues to receive messages and events. In fact, the timer expiration is just an event which you write a handler for.

If your thread must wait for something, your best performance will be achieved through the proper use of "Wait" type calls. WaitOne() and WaitAny() on an appropriate "WaitHandle" is always better performance wise than Sleep() because your thread waits only as long as it takes for the event to fire and the context switch to occur. Furthermore, there is no risk of the thread returning earlier than the event (unless you provide a timeout to "Wait").

How accurate is Sleep()? The Sleep time interval is specified to the millisecond. But Sleep() time expiration is serviced by the Operating System based upon its "tick" size. CPU ticks occur at a constant rate larger than a 1 millisecond granularity. So, threads will not wake at exactly the time span specified due to CPU tick granularity and scheduling priorities. They wake at their first scheduled opportunity after becoming "ready to run". They become ready to run at a time determined by how divisible the sleep time interval is by the CPU tick value. Sleeping threads become runnable on CPU Tick intervals. You can control the "tick" size (and thus the accuracy of Sleep) in C/C++ with timeGetDevCaps, timeBeginPeriod and timeEndPeriod.

By the way, while we are on the topic of C/C++, the call to SleepEx() is worth mentioning. SleepEx() is an alertable sleep function that awakes prior the elaspe of the time period when the same thread's I/O completion callback is called or when an APC function is called. The lines between "Sleep" and "Wait" begin to blur with this function.

In summary, use Sleep() as the "poor man's" Wait() function. Setting up a WaitHandle, synchronizing correctly, and handling timeouts with a "Wait" can be complex. Certainly, the "Wait" type calls would provide much lower latency, but if a few seconds of latency does not adversely affect your application, you don't need that added complexity. Sleep() is simple, use it simply. Never sleep on the GUI, or on the job, the consequences are not pretty.

That about wraps it up. There is no code today. Enjoy.

Friday, August 8, 2008

Threading with .NET ThreadPool Part 3

To wrap up this series of articles, I am going to make the application we've been working on do something a little more interesting. If you will recall in my first article on the subject of ThreadPool, I go over several ways to call the primary thread pool function QueueUserWorkItem and demonstrate how simple it is to multi-thread an application. In my second article, I look at a skeletal application that doesn't really do anything; it just displays messages on the console. However, and more importantly, it demonstrates some of the aspects of synchronization. You must synchronize if you are to do anything of real consequence with threads.

So, what sort of interesting things will this application do? My application will paint a pop-art picture. Actually, it will place random pixels on a PictureBox. Okay, its not that useful (or argueably that interesting), but it will demonstrate a means by which threads report progress to the GUI thread.

Let's look at the work item classes again and consider my updates. First, there are the changes to "WorkItem". I've added a public event handler called "Done" that will be fired by both of the derived classes. I've also added two public static properties called "Width" and "Height" which simply serve as a convenient place to store some global parameters for the work items to use. As a bonus, I've added some code to paint a background message that should materialize as the dots are painted.


public abstract class WorkItem
{
public abstract void ThreadPoolCallback(object context);
public event EventHandler Done;
public static int Width { get; set; }
public static int Height { get; set; }

private static bool _interrupted = false;
private static long _numWorkItems = 0;
protected static Bitmap _secretMessage;
protected static void InitMessage()
{
_secretMessage = new Bitmap(Width, Height);
Graphics g = Graphics.FromImage(_secretMessage);
g.FillRectangle(Brushes.White, 0, 0, Width, Height);
g.DrawString("Far Out!", new Font("Courier New",(float)18.0), Brushes.Black, (Width / 4), (Height / 2) - 15);
}

protected WorkItem()
{
Interlocked.Increment(ref WorkItem._numWorkItems);
}
protected void WorkItemDone()
{
Interlocked.Decrement(ref WorkItem._numWorkItems);
}
public static bool Interrupted
{
get { return _interrupted; }
set { _interrupted = value; }
}

public long NumWorkItems
{
get { return _numWorkItems; }
}

protected void OnDone()
{
if (Done != null)
Done(this, new EventArgs());
}
}


Next, the MasterWorkItem is modified to provide its "context" variable to each new ServiceWorkItems in the call to QueueUserWorkItem(). The "context" is declared as an object and is designed to let the programmer pass any reference he likes. We will be passing a reference to the GUI form. Furthermore, MasterWorkItem will call OnDone when completing normally to signal the Done event. The number of active work items has been increased to 1000. This increases the speed at which the painting is displayed. But, if you increase it too much, your CPU(s) will saturate and your GUI may not display the image (for me this was between 10-50 thousand work items. The painting will continually update until the GUI interrupts the master work item.


public class MasterWorkItem : WorkItem
{
public override void ThreadPoolCallback(object context)
{
try
{
WorkItem.InitMessage();
while (!Interrupted)
{
if (NumWorkItems < 1000)
{
ThreadPool.QueueUserWorkItem(new ServiceWorkItem().ThreadPoolCallback, context);
}
else
{
Console.WriteLine("Letting pool drain some");
Thread.Sleep(500);
}
}
while (NumWorkItems > 1)
{
Console.WriteLine("Waiting for child tasks {0}", NumWorkItems);
Thread.Sleep(1000);
}
Console.WriteLine("All tasks have completed.");
OnDone();
}
finally
{
WorkItemDone();
}
}
}


The ServiceWorkItem gets a new static method called "MyRand()" and a private static Random object. All the service threads use the same random number generator to determine the location and color of their pixel. Each service thread determines its pixel location and color and "tells" the GUI to paint it. Locks are used around structures that are not safe. Each work item calls OnDone when complete (but we don't implement a handler for it, so it goes ignored). Since threads cannot interact directly with the GUI, the service work item calls a GUI function that tests whether Invoke is required. BeginInvoke() is therefore our primary means of communication back to the GUI of our progress.


public class ServiceWorkItem : WorkItem
{
public override void ThreadPoolCallback(object context)
{
try
{
if (!Interrupted)
{
int X = ServiceWorkItem.MyRand(WorkItem.Width);
int Y = ServiceWorkItem.MyRand(WorkItem.Height);
int R = ServiceWorkItem.MyRand(255);
int G = ServiceWorkItem.MyRand(255);
int B = ServiceWorkItem.MyRand(255);
if (X > ((WorkItem.Width / 2) / 2)
&& X < ((WorkItem.Width / 2) / 2 + (WorkItem.Width / 2))
&& Y > ((WorkItem.Height / 2) / 2)
&& Y < ((WorkItem.Height / 2) / 2 + (WorkItem.Height / 2)))
{
R += 50; R = (R > 255) ? 255 : R;
//B += 50; B %= 256;
}
lock (WorkItem._secretMessage)
{
if (!WorkItem._secretMessage.GetPixel(X, Y).Name.Equals("ffffffff"))
{
R = 255;
B = 255;
G = 255;
}
}
Color clr = Color.FromArgb(R, G, B);

int myThread = Thread.CurrentThread.GetHashCode();
((Form1)context).SetPixelCallback(X,Y,clr);
}
else
{
Console.WriteLine("Thread {0}, WorkItem {1}, Interrupted",
Thread.CurrentThread.GetHashCode().ToString(),
this.GetHashCode().ToString());
}
OnDone();
}
finally
{
WorkItemDone();
}
}
private static Random rnd = new Random();
public static int MyRand(int limit)
{
return rnd.Next(0, limit);
}
}


Finally, the main form (Form1) is changed to provide a Bitmap, a function to update the bitmap, and a PictureBox to display it. The Form1_Load handler initializes the bitmap, paints it white and hooks it up to the pictureBox1. The Load handler also initializes the work item globals, creates a new master work item, wires up the work item's "Done" handler, and starts the master thread.


public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Bitmap bitmap;
private Random rnd;

void m_Done(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new MethodInvoker(() => this.label1.Text = "Done"));
}
else
{
this.label1.Text = "Done";
}
}
private void button1_Click(object sender, EventArgs e)
{
WorkItem.Interrupted = true;
}

private void Form1_Load(object sender, EventArgs e)
{
bitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);
Graphics g = Graphics.FromImage(bitmap);
g.FillRectangle(Brushes.White, 0, 0, bitmap.Width, bitmap.Height);
pictureBox1.Image = bitmap;

rnd = new Random();

WorkItem.Width = bitmap.Width;
WorkItem.Height = bitmap.Height;
MasterWorkItem m = new MasterWorkItem();
m.Done += new EventHandler(m_Done);
ThreadPool.QueueUserWorkItem(m.ThreadPoolCallback,this);
}

internal delegate void SetPixelDelegate(int x, int y, Color clr);
internal void SetPixelCallback(int X, int Y, Color clr)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new SetPixelDelegate(SetPixelCallback), new object[] { X, Y, clr } );
}
else
{
bitmap.SetPixel(X, Y, clr);
}
}
private void timer1_Tick(object sender, EventArgs e)
{
pictureBox1.Image = bitmap;
}
}


And, here is the code for Form1's code-behind (Form.Designer.cs)...


partial class Form1
{
private System.ComponentModel.IContainer components = null;

protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.button1 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.pictureBox1 = new System.Windows.Forms.PictureBox();
this.timer1 = new System.Windows.Forms.Timer(this.components);
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(29, 217);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 26);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(124, 224);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 1;
this.label1.Text = "label1";
//
// pictureBox1
//
this.pictureBox1.Location = new System.Drawing.Point(25, 28);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(239, 162);
this.pictureBox1.TabIndex = 2;
this.pictureBox1.TabStop = false;
//
// timer1
//
this.timer1.Enabled = true;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.pictureBox1);
this.Controls.Add(this.label1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();

}


private System.Windows.Forms.Button button1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.Timer timer1;
}


I call my namespace "ThreadPoolApp" and here are my "using" statements as well...


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadPoolApp
{
//...
}


To review, there are several aspects of a multi-threaded that in my opinion should be present to be a robust and stable app. These are...

1. Enqueuing work.
2. Detecting queue overload and throttling.
3. Detecting work start.
4. Reporting work progress.
5. Detecting work completion.
6. Stopping work-in-progress.
7. Cancelling un-started work.

But, I've done nothing in my code to detect the starting of work (#3). I leave it to the reader as an exercise, but for something this simple, I would probably use an Invoke to change a visible label since the startup is obvious by observing the updating of the picture. Of course, others may have their own list, and depending upon the application, I may not implement all of these suggestions myself.

To wrap up, let's consider what kind of applications benefit from such an architecture. An application benefits where one or a few threads need to listen for incoming network requests and then pass off a (short lived) work item for processing while it goes back to listening. If you are considering writing a loop for a thread to wait for work and effectively do the same thing or kind of things over and over, your application could probably benefit from this approach. There are several architectural choices when it comes to multi-threading, and that choice is yours. I hope these articles can help your deliberations.

Thursday, August 7, 2008

Threading with .NET ThreadPool Part 2

Let's continue looking at System.Threading.ThreadPool. In the last article, we went over some of the basic features a thread pool should have. We looked specifically at System.Threading.ThreadPool to see how to get your App threading quickly. And, we touched on some aspects missing from Microsoft's thread pool implementation that are needed for an "industrial strength" application. So today, we will examine some of those missing features.

Let's start with a skeleton application. This application starts a master work item that is responsible for feeding service work items to the thread pool. The service work items do some work (right now, they display a message with Console.WriteLine(...) and then sleep a random amount of time in the sub-second range). We get a taste of synchronization with the shared random number generator where I surround it with a lock(...){ } statement to make it thread safe. When service work items are done, they exit. "Exit" is a term you should be careful with. Often, it means an explicit call to some function to exit a program. Here we mean, the thread returns from the callback function. So, it's our task that's exiting, not the thread.

This skeletal application also has a button to interrupt the master work item's thread, which in turn interrupts the service work item threads and waits for their completion which should occur within 1 second or so. To facilitate a clean shutdown, I employ a little more thread synchronization using a simple task counter.

Here's the code. To use it, you can create a new Windows Forms project named ThreadPoolApp, drop a button on it, then view the source code for Form1.cs. Copy the code below and replace the code in Form1.cs. Last, set your button's event handler to use the one in my code.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadPoolApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ThreadPool.QueueUserWorkItem(new MasterWorkItem().ThreadPoolCallback);
}
private void button1_Click(object sender, EventArgs e)
{
WorkItem.Interrupted = true;
}
}
public abstract class WorkItem
{
public abstract void ThreadPoolCallback(object context);

private static bool _interrupted = false;
public static bool Interrupted {
get { return _interrupted; }
set { _interrupted = value; }
}
private static long _numWorkItems = 0;
protected WorkItem()
{
Interlocked.Increment(ref WorkItem._numWorkItems);
}
protected void WorkItemDone()
{
Interlocked.Decrement(ref WorkItem._numWorkItems);
}
protected long NumWorkItems
{
get { return _numWorkItems; }
}
}
public class MasterWorkItem : WorkItem
{
public override void ThreadPoolCallback(object context)
{
try
{
while (!Interrupted)
{
if (NumWorkItems < 500)
{
ThreadPool.QueueUserWorkItem(new ServiceWorkItem().ThreadPoolCallback);
}
else
{
Console.WriteLine("Letting pool drain some");
Thread.Sleep(500);
}
}
while (NumWorkItems > 1)
{
Console.WriteLine("Waiting for child tasks {0}", NumWorkItems);
Thread.Sleep(1000);
}
}
finally
{
WorkItemDone();
}
}
}
public class ServiceWorkItem : WorkItem
{
public override void ThreadPoolCallback(object context)
{
try
{
if (!Interrupted)
{
int val = ServiceWorkItem.MyRand();
int myThread = Thread.CurrentThread.GetHashCode();
Console.WriteLine("Thread {0}, WorkItem {1}, Sleeping {2}",
myThread.ToString(),
this.GetHashCode().ToString(),
val.ToString());
Thread.Sleep(val);
Console.WriteLine("Thread {0}, WorkItem {1}, done.", myThread.ToString(), this.GetHashCode().ToString());
}
else
{
Console.WriteLine("Thread {0}, WorkItem {1}, Interrupted",
Thread.CurrentThread.GetHashCode().ToString(),
this.GetHashCode().ToString());
}
}
finally
{
WorkItemDone();
}
}
private static Random rnd = new Random();
public static int MyRand()
{
lock (rnd)
{
return rnd.Next(100, 1000);
}
}
}
}


I've created three classes, an abstract WorkItem class, the MasterWorkItem class and the ServiceWorkItem class. The MasterWorkItem and the ServiceWorkItem classes derive from the WorkItem class which enforces that the derived classes implement the CallBack according to the function signature required by the the static ThreadPool method QueueUserWorkItem(). The WorkItem class implements a control member whereby I can stop the Master and Service work items. Each work item has its own instance data that the callback can refer to. Each can also refer to some global information in the WorkItem static class.

Now, if you run the code, you will start seeing text information in the debugger output window. The master is limited to having only 500 tasks active. It goes to sleep and lets the queue "drain" a little bit when it hits this limit.

When you click the button after a few seconds, you will see output from the tasks being terminated. The threads are not necessarily terminating, they are just pulling tasks from the queue and the callbacks are returning immediately after reporting that they have been interrupted. When the task count drops to 1, the master work item (task) can return from it's callback.

Maybe you see "thread xxx has exited" messages. I have said that with the thread pool, tasks exit and not threads. So, why is it then that we see messages from the debugger that such-and-such thread has exited? That is because the thread pool is managing it's resources. If it doesn't need as many active threads, some of the threads exit. But that is not a concern of our application, we are only concerned about our task exiting. Whether the thread pool starts or stops threads to service our app is completely under the hood.

As I mentioned in my last article, getting multiple threads working in your application is almost trivial with the ThreadPool, but you must deal with several aspects of thread synchronization to have a useful app. Again, these aspects are...

1. Enqueuing work.
2. Detecting queue overload and throttling.
3. Detecting work start.
4. Reporting work progress.
5. Detecting work completion.
6. Stopping work-in-progress.
7. Cancelling un-started work.

This sample application demonstrates several of these aspects. Of course, it enqueues work, the Master work item self-throttles with a simple count and sleep mechanism. Work completion is detected at the high level by the Master task with a task counter, but it is a weak method in that we cannot really tell which work item completes. Cancelling un-started work and stopping work-in-progress is accomplished with a simple state variable shared by all tasks called "Interrupted". This application does not really report progress or indicate when a task starts (I don't consider debug messages to the console to be accomplishing that). I will try to flesh out the progress communication and startup notification in my next article.

Before I leave, I have just a quick word about thread synchronization. You will notice that I am not using anything fancy like AutoResetEvent, or Mutex, or Monitor, or the Wait{...} type calls. I use Interlock.Increment() and Interlock.Decrement() to manage the changing of the task count, and I use lock(){...} to protect the Random number generator. Even there, the lock() may not be necessary, but the documentation does not say the Random.Next() call is thread safe, so I add it. I can get away with my Interrupted flag being a simple variable because I know there is only one thread that will ever write to it. Threads that read from Interrupted are not harmed by rogue timings or simultaneous access. Similarly, reading the task count is not protected because timing of reads and writes do not significantly affect the result.

In my next article, I will provide more on fancier synchronization, and demonstrate some progress reporting and startup detection.

Tuesday, August 5, 2008

Threading with .NET ThreadPool

The System.Threading.ThreadPool is a great little feature for programmers wishing to add instant threading to their applications. You just enqueue callback functions, what could be more simple than that? I agree, so we'll look now at some simple coding experiments with the ThreadPool. By the way, here's the MSDN version of "How to: Use a Thread Pool" which makes for some good pre-requisit reading. And though I risk repeating much of what may be already elsewhere on the web, I hope that I can add something of use in your estimation.

So first, let's get a basic understanding of thread pooling. Maybe you've already read my article, Thread Synchronized Queing. If so, you have a good start. A thread pool, like my SynchQueue class, will involve some thread(s) writing to a queue, and some set number of threads reading from the queue. Similarly, if there isn't enough work to do, the reader threads wait or block on the queue. And, if there is too much work to do, the queue grows indefinitely or until you throttle your writer threads somehow.

Let's start with a simple Windows Forms application. Later, I'll come back and discuss some of the caveats. I create a new application called ThreadPoolExample. To Form1 I add a couple of methods that I will use as callback functions. One is an instance method and the other a static class method. I also add a new class to Form1.cs called TestClass. TestClass also has a callback function defined.


private void CallBack(object Context)
{
Console.WriteLine("Thread called");
}
private static void SCallBack(object Context)
{
Console.WriteLine("Thread static callback");
}
...
public class TestClass
{
public void CallBack(object context)
{
Console.WriteLine("TestClass #{0}",_mynum);
}
int _mynum;
public TestClass(int n) { _mynum = n; }
}



Now, I add a button to the form and add the following click handler for the button.


private void button1_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(this.CallBack);
ThreadPool.QueueUserWorkItem(new WaitCallback(this.CallBack));
ThreadPool.QueueUserWorkItem(SCallBack);
ThreadPool.QueueUserWorkItem((o) => { Console.WriteLine("lamda callback"); });
ThreadPool.QueueUserWorkItem(delegate(object o) { Console.WriteLine("delegate callback"); });

int max, dummy;
ThreadPool.GetMaxThreads(out max, out dummy);
for (int i = 0; i < max + 20; ++i)
ThreadPool.QueueUserWorkItem(new TestClass(i).CallBack);
}


With this click handler, I am demonstrating several aspects of starting work with a ThreadPool. In all cases, I start a thread working with the static ThreadPool method QueueUserWorkItem(). This method gives the task to the first available thread, so most of the calls start (or can start) immediately. I say, "start a thread working" rather than "start a thread" because I am doing nothing to start threads. All the threads are started by the underlying framework. It is the framework that controls the maximum number of threads to start and the minimum number of threads to have running in standby. I simply enqueue work to perform and let the framework manage the threads.

The first two calls show the ThreadPool being given an instance method. One call just provides the name of the method, while the other call uses the familiar "new Delegate(function)" construction. I added both of these because I wanted to see if there are any real differences in the two calling methods. I can't see any except I presume the second may not perform as well as the first due to the additional memory allocation. By using the "new" operator, you get the help of Intellisense in figuring out how to write the callback signature. Other than that, I think it's purely up to programmer style. The third line provides a static class method as the callback.

The fourth line demonstrates passing a Lamda Expression to the ThreadPool. Lamda Expressions are new to Visual Studio 2008. Comment it out if you are using 2005 or earlier. The fifth line demonstrates passing an anonymous function to the ThreadPool. Lastly, I use the ThreadPool static method GetMaxThreads() to figure out how many threads the pool will use, and then I exceed that number by 20. The last set of calls also demonstrates passing instance methods of different object instances. This is handy because the instance method has direct access to the object's encapsulated data. In other words, you can implement a class to represent small units of work and hand these off to the ThreadPool by their callback method thus maintaining separate states for each.

You might also note, the ThreadPool.QueueUserWorkItem() is given methods to different classes. The ThreadPool does not care about the type of the underlying data, only the signature of the callback you pass to it. This makes the tool quite versatile. But, similar behavior is gotten from the SynchQueue generic class by declaring it to use a delegate type instead of a data type.

Now, run the code and click the button. You will see in Visual Studio's output window the various strings the threads are supposed to write. Everything works fine. Take a closer look at the output and you will see that things mostly run in the order that they were enqueued. But some of the tasks appear out of order. In fact, the callbacks are assigned a thread in the order that they were enqueued, but there is no guarantee that the threads will finish in the same order as the work was queued. This is fairly intuitive, tasks can take varying amounts of time to complete.

But also consider, though the work is dequeued by threads in FIFO order, it is entirely possible that some tasks will actually start out of order. Picture there being multiple tasks on the queue ready to go, and picture one thread grabbing a task and then immediately being preempted. Another thread grabs the next task and begins work before the first thread comes back.

Thus, we have our first caveat. That is, don't expect the ThreadPool to start your tasks in a fixed order. If you need them started in a precise, fixed order, you will need to synchronize your tasks in some other way.

Like I said earlier, getting multiple threads working in your application is almost trivial with the ThreadPool. But alas, multi-threading is never trivial. There are several aspect of thread pooling that should be addressed if you wish to have an industrial strength application. These are...

1. Enqueuing work.
2. Detecting queue overload and throttling.
3. Detecting work start.
4. Reporting work progress.
5. Detecting work completion.
6. Stopping work-in-progress.
7. Cancelling un-started work.

We've only looked at enqueuing work. But, without the ability to know for sure that the work has been fully completed by the thread, it makes any amount of interdepenancy risky and a clean shutdown nearly impossible. You may have need to end your application or server while tasks are still running. To do this cleanly, you need a way to stop adding tasks to the queue, to cancel any un-started tasks on the ThreadPool queue, and to stop tasks currently being processed by a ThreadPool thread.

I'll look at each of these in my next article. Until then, here is the full source for Form1.cs (you'll need to add your own button and hook up the button click handler yourself).


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadPoolExample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void CallBack(object Context)
{
Console.WriteLine("Thread called");
}
private static void SCallBack(object Context)
{
Console.WriteLine("Thread static callback");
}
private void button1_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(this.CallBack);
ThreadPool.QueueUserWorkItem(new WaitCallback(this.CallBack));
ThreadPool.QueueUserWorkItem(SCallBack);
ThreadPool.QueueUserWorkItem((o) => { Console.WriteLine("lamda callback"); });
ThreadPool.QueueUserWorkItem(delegate(object o) { Console.WriteLine("delegate callback"); });

int max, dummy;
ThreadPool.GetMaxThreads(out max, out dummy);
for (int i = 0; i < max + 20; ++i)
ThreadPool.QueueUserWorkItem(new TestClass(i).CallBack);
}
}

public class TestClass
{
public void CallBack(object context)
{
Console.WriteLine("TestClass #{0}",_mynum);
}
int _mynum;
public TestClass(int n) { _mynum = n; }
}
}