Google
WWW Yariv Hammer's Code Site

Friday, March 03, 2006

MultiThreading Without The Thread Class

In this serie of articles I show a lot of the mechanisms we have in .NET to program MultiThreaded applications and to perform asynchronous operations.
Here are the articles in this serie:
Asynchronous Programming in .NET
Synchronizing Threads in .NET
MultiThreading Without The Thread Class

Introduction
In this article I will show several mechanisms of using threads without actually using the Thread class.

Using Timers
There are three types of Timers in the .NET frmawork. System.Windows.Forms.Timer is used in UI forms, and works on Windows messages. This is the least efficient timer.
The System.Timers.Timer and the System.Threading.Timer both work with the Windows timer, and they are quite similar in performance. The System.Timers.Timer is more user-friendly, because it has a Start and Stop methods, an Interval property and so on.

The System.Threading.Timer will invoke a method at a constant rate.
------------------------------------------
public void myMethod(object obj)
{
//Do something
}

public static void Main()
{
Timer t = new Timer(new TimerCallBack(myMethod), anyObject, 0, 1000);
Console.ReadLine();
}
---------------------------------------------
The timer can work on any method that receives an object. The second parameter will be passed to the method as a parameter (pass null if you don't need it). The Thirs argument is how much time (in milliseconds) to wait before starting the timer, and the last parameter is the period of time between to invokations.
The threads are taken out of the Thread Pool, and they run in the background.
When you are done ith the timer, Dispose it.

Using the ThreadPool
If you need to perform a lot of short tasks (a worker thread for example), and then get rid of the threads you can use the ThreadPool to improve performance. When you start your application Windows generates 25 threads for you to use (Actually sometimes, as in timers, the CLR will use threads from the pool). The number of threads in the pool cannot be configured in 2003, so be careful not to use too many threads. If you use a thread from the ThreadPool you save the time of creating and destroying the thread. You just ask for a free thread, and when you are done it goes back to the pool.

Whenever you need to call the QueueUserWorkItem static method to start a thread from the thread pool.
For example:
------------------------------------------
public void myMethod(object obj)
{
//Do something
}

public static void Main()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(myMethod), anyObject);
Console.ReadLine();
}
---------------------------------------------
Like in the timer, you should pass a method that receives an object, and that object as second parameter.
For another example look at MSDN.

Those threads are intended for short actions, that run in the backgroound, with normal priority (you cannot change any of it). You might want to pass a ManualResetEvent as the parameter to the method if you want to be able to synchronize the thread.

Asynchronous Delegates
A reminder about the usage of delegates:
---------------------------------------------------
class Class1
{
public static void f1(int x)
{
for (int i=0; i<x; i++)
{
Console.WriteLine("f1: " + i);
Thread.Sleep(300);
}
}
public delegate void Fptr(int x);
static void Main(string[] args)
{
Fptr p1 = new Fptr(f1);
p1(50);
Console.WriteLine("Main End");
Console.ReadLine();
}
}
----------------------------------------------------
You call the delegate as you call a normal method in order to invoke it. This is called Syncronous Invocations.

Instead we could call the BeginInvoke method of the delegate:
---------------------------------------------------
static void Main(string[] args)
{
Fptr p1 = new Fptr(f1);
p1.BeginInvoke(50,null,null);
Console.WriteLine("Main End");
Console.ReadLine();
}
----------------------------------------------------
You will see that the Main End will ve written as the program starts, to indicate that f1 runs on a different thread. This thread is taken from the Thread Pool.

If we want to return a value from the method f1, we will need to use EndInvoke:
-------------------------------------------------------
public static int f1(int x)
{
for (int i=0; i<x; i++)
{
Console.WriteLine("f1: " + i);
Thread.Sleep(100);
}
return x;
}
public delegate int Fptr(int x);
static void Main(string[] args)
{
Fptr p1 = new Fptr(f1);
IAsyncResult ar = p1.BeginInvoke(50, null, null);
Console.WriteLine("Main Sleeping");
Thread.Sleep(1000);
int res = p1.EndInvoke(ar);
Console.WriteLine("Main End " + res);
Console.ReadLine();
}
------------------------------------------
The method f1 now returns an int. When we call BeginInvoke we are returned with an object of type IAsyncResult. We pass that object to the EndInvoke method and then we wait until the method f1 is done (If it is over when we get to the line we don't wait at all), and we receive the return value. The use of IAsyncResult is because we may start p1 many times, and the EndInvoke should know which time we want to end. The IAsyncResult uniquly identifies the invocation.
Note that EndInvoke blocked the code. A nice propery of IAsyncResult is the IsComplete property which can be called in a loop to know if the method is over (A polling approach).

Another approach might be to receive an event when the method is done. This is the best approach because the code will never be blocked.
For example:
------------------------------------------
public static void f1IsCompleted(IAsyncResult ar)
{
Console.WriteLine("F1 is done");
}
static void Main(string[] args)
{
Fptr p1 = new Fptr(f1);
p1.BeginInvoke(50, new AsyncCallback(f1IsCompleted), null);
Console.WriteLine("Main Sleeping");
Thread.Sleep(1000);
Console.WriteLine("Main End");
Console.ReadLine();
}
--------------------------------------------------------
The method f1IsCompleted receives the IAsyncResult as a parameter. It is called automatically when the f1 method is completed.

The last thing to see is how to retrieve the return value of the method f1. Here is one way to do it:
----------------------------------------------------------
public static void f1IsCompleted(IAsyncResult ar)
{
int res = ((Fptr)ar.AsyncState).EndInvoke(ar);
Console.WriteLine("F1 is done: " + res);
}
static void Main(string[] args)
{
Fptr p1 = new Fptr(f1);
p1.BeginInvoke(50, new AsyncCallback(f1IsCompleted), p1);
Console.WriteLine("Main Sleeping");
Thread.Sleep(1000);
Console.WriteLine("Main End");
Console.ReadLine();
}
-------------------------------------------------
The last parameter of BeginInvoke can be any object. This object can be retrieved as the IAsyncResult.ASyncState property in the callback method. We use this mechanism to pass the delegate, in order to call the EndInvoke after the callback is raised.
Another way to do it was to place the Fptr p1 as a member of the class, so the f1IsCompleted method can access it.

Using Threads With User Interface
Here is a rule: Never access the GUI from any thread other than the GUI thread.
Lets have an axample: We have a form with a ListBox and a Button. We write the following Click event handler for the Button:
---------------------------------------------------
private void button1_Click(object sender, EventArgs e)
{
for (int i=0; i<10000 ; i++)
listBox1.Items.Add(i);
}
-------------------------------------------------
If you run the program and press the button you will see that the form gets stuck until the ListBox is filled with numbers. You might even get a (not responding) in the title of the form.
The reason for this is that if you perform a long task in the event handlers of the form, you are performing it in the GUI thread. No Windows messages can be handled when the GUI thread is working on something else. For that reason you should only perform short taskd in event handlers. Long tasks should be performed in different threads.

Lets look at the following fix:
---------------------------------------------------
private void GenerateNumbers()
{
for (int i=0; i<10000 ; i++)
listBox1.Items.Add(i);
}

private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(GenerateNumbers));
t.Start();
}
-------------------------------------------------
If you run the program and press on the button you get a respons from the form while the ListBox is filled.
You should never do it this way!!! We are accessing the GUI from different thread. In 2005 you will even get an exception.

The correct way of doing this is by calling the Invoke method of the form. This will call a method in the GUI thread:
---------------------------------------------------
private void GenerateNumbers()
{
for (int i=0; i<10000 ; i++)
{
this.Invoke(new Fptr(InsertNumberToList), new object[] {i});
Thread.Sleep(10);
}
}
public delegate void Fptr(int n);
private void InsertNumberToList(int n)
{
listBox1.Items.Add(i);
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(GenerateNumbers));
t.Start();
}
-------------------------------------------------
The Invoke method receives a delegate (any delegate) and an array of parameters to pass to the delegate. We are guaranteed that the method will be executed in the GUI thread. However, without the Sleep call the GUI would be frozen, because the loop constantly updates the GUI. The Sleep will make sure that the user gets enough time to interact with the form while the loop is running.

If you run the program you will notice that the ListBox is updating much slower than before. The Sleep we placed is the cause of that. This is the tradeoff we get by performing long tasks which interacts with the UI: Good response from the UI vs. Time it takes to complete the task.

By the way, this method is useful while updating ProgressBars from worker threads.

In 2005 we have a BackGroundWorker control that we can drag to the component tray of the form. This control can manage tasks in backgorund threads for us in a much more user-friendly way.

Summary
In this article I showed how to perform asyncronous operations in a MultiThreaded environment without actually using the Thread class. I showed how to use timers, how to call asyncronous delegates, how to call Invoke in UI applications, and how to use the ThreadPool.

0 Comments:

Post a Comment

<< Home

Feel free to use everything here. Add links to my site if you wish.

Do not copy anything to other sites without adding link to here.

All the contents of the site belong to Yariv Hammer.