| .NET offers us the ability to execute proccesses via threading and thread pooling. The following C# code sample is not meant to be a scientific approach to calculating time spent processing threads but just an example of how you may be able to utilize them in certain situations. I've run across several examples of how to use threading. Almost all of them display counters that make it appear that the threads are running processes simultaneously. Out of curiousity, I wanted to put a small C# application together to test this notion and time its performance. |
| |
|
|
| |
| It is important to understand that utilizing threads does not necessarily mean that these threads are processed simultaneously especially on single processor computers. A processor can only handle one process at a time and the operating system temporarily suspends a process and allows others to start or finish. This is what allows you to run multiple applications seemingly at once and is often referred to as pre-emptive multi-tasking. In .NET, you have the ability to use the Thread object and control (to a certain extent) when a thread should pause and for how long. You can also, as we've done here today, utilize the built in classes and objects for thread pooling and let the operating system and the .NET runtime manage that for you. |
| In the Test 1 below, you can watch the operating system switch back and forth every few milliseconds to process one thread instead of the other. On single processor computers, you are likely to see the single thread test run faster than the multi-thread test in certain situations. In this small test, each thread is not required to wait for a completion response before continuing. Knowing that the single processor can only handle one thread at a time, we force it to work a little harder by switching back and forth between threads. |
| I've done similar tests which search for files on the hard drive and found similar results. However, on multi-processor computers and servers, the dual thread test often performed anywhere from 10-20% faster than the single thread test. |
| In the Test 2, I opted to see how thread pooling is affected when each thread process needs to wait for a response prior to continuing. A good example of such a process is an http request. Theoretically, the multi-thread test should perform upwards of 50% faster even on a single processor computer because it should be able to allow the other thread to continue its request while thread 1 waits for a response. Depending on the web site load time (I tested several different sites), the success or failure of the multi-thread scenario to perform faster varied. The slower the web site response, the better the multi-scenario performed. |
| The big difference showed itself in Test 2 on multi-processor machines even more so than Test 1. Having multiple processors processing and waiting on requests greatly improved our performance. In some cases, as much as 50%. The operating system was free to process a new request more frequently in this environment. |
| Test 3 spawns threads dynamically with one thread for each attempt. I've mocked up a way to check to see if all threads have completed regardless of whether they failed or not. On multi-processor computers, its performance was the best so far. However, if you choose to implement this solution, you'll want to pay close attention to the number of threads that are available for your thread pool. You'll also need to trap for failures because of a lack of threads available in the pool and reprocess those attempts when your current set of threads are done. I left the code for test 3 in for you to play around with a bit and experiment. |
| Now that you know a little more about threading in .NET than you did yesterday, I'd suggest looking for ways to implement thread pooling in some of your own scheduled jobs and processes. You may be able to substantially improve their performance. As always, we look forward to your participation in our forums and rating our articles. Feel free to click the links below the sample code in order to participate. |
| |
| Sample Code |
using System;
using System.Threading;
using System.IO;
using System.Net;
using System.Windows.Forms;
using System.Web.Services;
namespace prjThreadPool
{
class cThreadPool
{
//
[STAThread]
static void Main(string[] args)
{
// ThreadTest1 Test1 = new ThreadTest1();
ThreadTest2 Test2 = new ThreadTest2();
ThreadTest3 Test3 = new ThreadTest3();
// Test1.ProcessTest();
Test2.ProcessTest();
Test3.ProcessTest();
MessageBox.Show("done");
}
}
public class ThreadTest1
{
public void ProcessTest()
{
System.DateTime sStartTime;
TimeSpan elapsed1;
TimeSpan elapsed2;
sStartTime = System.DateTime.Now;
Console.WriteLine("Multi-thread start " + System.DateTime.Now.ToString());
AutoResetEvent MyThread1 = new AutoResetEvent(false);
AutoResetEvent MyThread2 = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyThreadProcess1), MyThread1);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyThreadProcess2), MyThread2);
MyThread1.WaitOne();
MyThread2.WaitOne();
elapsed1 = System.DateTime.Now - sStartTime;
Console.WriteLine("Multi-thread test done. " + elapsed1.TotalSeconds.ToString());
sStartTime = System.DateTime.Now;
Console.WriteLine("Single thread start " + System.DateTime.Now.ToString());
for (int i=0;i<20000000;i++) { if(i%1000 ==0) {Console.WriteLine("Thread 3: " + i.ToString()); }}
elapsed2 = System.DateTime.Now - sStartTime;
Console.WriteLine("Single thread test done. " + elapsed2.TotalSeconds.ToString());
Console.WriteLine("Summary Multi-Thread " + elapsed1.TotalSeconds.ToString());
Console.WriteLine("Summary Single-Thread " + elapsed2.TotalSeconds.ToString());
}
static void MyThreadProcess1(Object state)
{
for (int i=0;i<10000000;i++) { if(i%1000 ==0) {Console.WriteLine("Thread 1: " + i.ToString()); }}
((AutoResetEvent)state).Set();
}
static void MyThreadProcess2(Object state)
{
for (int i=0;i<10000000;i++) { if(i%1000 ==0) {Console.WriteLine("Thread 2: " + i.ToString()); }}
((AutoResetEvent)state).Set();
}
}
public class ThreadTest2
{
public void ProcessTest()
{
System.DateTime sStartTime;
TimeSpan elapsed1;
TimeSpan elapsed2;
sStartTime = System.DateTime.Now;
Console.WriteLine("Multi-thread start " + System.DateTime.Now.ToString());
AutoResetEvent MyThread1 = new AutoResetEvent(false);
AutoResetEvent MyThread2 = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyThreadProcess1), MyThread1);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyThreadProcess2), MyThread2);
MyThread1.WaitOne();
MyThread2.WaitOne();
elapsed1 = System.DateTime.Now - sStartTime;
Console.WriteLine("Multi-thread test done. " + elapsed1.TotalSeconds.ToString());
sStartTime = System.DateTime.Now;
Console.WriteLine("Single thread start " + System.DateTime.Now.ToString());
MyHttp oHttp = new MyHttp();
oHttp.ProcessThread(3,20);
elapsed2 = System.DateTime.Now - sStartTime;
Console.WriteLine("Single thread test done. " + elapsed2.TotalSeconds.ToString());
Console.WriteLine("Summary Multi-Thread " + elapsed1.TotalSeconds.ToString());
Console.WriteLine("Summary Single-Thread " + elapsed2.TotalSeconds.ToString());
}
static void MyThreadProcess1(Object state)
{
MyHttp oHttp = new MyHttp();
oHttp.ProcessThread(1,10); // Thread 1
((AutoResetEvent)state).Set();
}
static void MyThreadProcess2(Object state)
{
MyHttp oHttp = new MyHttp();
oHttp.ProcessThread(2,10); // Thread 2
((AutoResetEvent)state).Set();
}
}
public class ThreadTest3
{
public static int nSuccessfulTrys;
public static int nFailedTrys;
static string sURL = "http://www.robbemorris.com";
public void ProcessTest()
{
int nWThreads;
int nWCompPortThreads;
int nAttempts=20;
System.DateTime sStartTime;
TimeSpan elapsed1;
sStartTime = System.DateTime.Now;
Console.WriteLine("Dynamic Multi-thread start " + System.DateTime.Now.ToString());
AutoResetEvent MyThread1 = new AutoResetEvent(false);
ThreadPool.GetAvailableThreads(out nWThreads,out nWCompPortThreads);
if (nAttempts > nWThreads) { nAttempts = nWThreads -2; }
for (int i=0;i<nAttempts;i++)
{ThreadPool.QueueUserWorkItem(new WaitCallback(MyThreadProcess1), MyThread1); }
while ((nSuccessfulTrys + nFailedTrys) < nAttempts)
{System.Threading.Thread.Sleep(5);}
elapsed1 = System.DateTime.Now - sStartTime;
Console.WriteLine("Dynamic Multi-thread end " + elapsed1.TotalSeconds.ToString());
}
public static void MyThreadProcess1(Object state)
{
string sResp = null;
int nWThreads;
int nWCompPortThreads;
try
{
ThreadPool.GetAvailableThreads(out nWThreads,out nWCompPortThreads);
if (nWThreads > 1)
{
HttpWebRequest oWebReq = (HttpWebRequest)WebRequest.Create(sURL);
HttpWebResponse oWebResp = (HttpWebResponse)oWebReq.GetResponse();
StreamReader oStream = new StreamReader(oWebResp.GetResponseStream(),System.Text.Encoding.ASCII );
sResp = oStream.ReadToEnd();
if (sResp.Length > 0) {++nSuccessfulTrys;}
}
else
{
Console.WriteLine("Not enough threads: " + nWThreads.ToString());
++nFailedTrys;
}
}
catch(Exception HttpEx)
{
Console.WriteLine(HttpEx.Message + " " + nFailedTrys);
++nFailedTrys;
}
((AutoResetEvent)state).Set();
}
}
public class MyHttp
{
public void ProcessThread(int nThread,int nMaxTrys)
{
string sURL = "http://www.robbemorris.com";
string sResp = null;
try
{
for (int i=0;i<nMaxTrys;i++)
{
HttpWebRequest oWebReq = (HttpWebRequest)WebRequest.Create(sURL);
HttpWebResponse oWebResp = (HttpWebResponse)oWebReq.GetResponse();
StreamReader oStream = new StreamReader(oWebResp.GetResponseStream(),System.Text.Encoding.ASCII );
sResp = oStream.ReadToEnd();
}
}
catch(Exception HttpEx){Console.WriteLine(HttpEx.Message); }
}
}
}
|