Google
WWW Yariv Hammer's Code Site

Thursday, March 02, 2006

Asynchronous Programming in .NET

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
Here are four important classes regrading the mangement of processes and threads in the operating system:
- AppDomain - A class that represent a .NET process, in the context of the CLR. Each .NET application is opened as an AppDomain, and later on can open several other AppDomains.
- Thread - A class that represents a managed thread.
- Process - A class in the System.Diagnostics namespace that represents the process from the Win32 point of view. It is mainly for monitoring process information.
- ProcessThread - A class that represents a thread from Win32 point of view, and is used to monitor thread information.
Each .NET application starts 9 thread normally on startup. Only one of them is the managed thread where your code runs. A .NET thread is a Win32 thread, but the other way is not true always. The following line will show you the exact number of threads in your application:
Console.WriteLine(Process.GetCurrentProcess().Threads.Count)
You use the Process class in order to start new processes (by calling the Start method) or to receive information about other processes. The Threads property return a collection of ProcessThread objects. The mthod EnterDebugMode for example can perform the same operation as the TaskManager. ProcessThread for example can set the Processor Affinity.

AppDomain contains several properties regarding the .NET running assembly, and enables to communicate between several AppDomains. We use AppDomains when we want to wrap the environment with security. It is a virtual environment to which we load assemblies and run them. We do not need to worry about releasing resources on exit (The CLR takes care of that for us). We pay with performance of copying data between the AppDomains.

The Thread Class
The Thread class contains static and member methods. We use it when we want to do things in parallel or asynchronously. The threads, unlike AppDomains, share a memory space and resources. Each thread has an entry point, which is a method without parameters which returns no value.
Here is a small example to demonstrate:
---------------------------------------------------
class A
{
public void f1()
{
for (int i=0; i<10;i++)
{
Console.WriteLine("f1: " + i);
Thread.Sleep(500);
}
}
public static void f2()
{
for (int i=0; i<10; i++)
{
Console.WriteLine("f2: " + i);
Thread.Sleep(700);
}
}
}
class Class1
{
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(A.f2));
A a = new A();
Thread t2 = new Thread(new ThreadStart(a.f1));
t1.Start();
t2.Start();
Console.WriteLine("Main End");
//Console.ReadLine();
}
}

--------------------------------------------------
We have three threads opened: t1, t2 and main. The output starts with the "Main End" message, because the t1 and t2 threads runs in parallel to the main method. The Thread.Sleep method applies to the current thread, and stops the thread for a while. The ThreadStart is a delegate to a void method without parameters. (In 2005 there is an option to pass an object). The thread can have a priority, Which is one of 5 levels. Normal is the defaut.

BackGround Threads
The application will not finish until all threads are over. If you wish the application to end when the main thread is over, you should set the IsBackGround property of the thread to be true:
--------------------------------------------
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(A.f2));
A a = new A();
Thread t2 = new Thread(new ThreadStart(a.f1));
t1.IsBackGround = true;
t2.IsBackGround = true;
t1.Start();
t2.Start();
Console.WriteLine("Main End");
}
--------------------------------------------
The main method will stop right away, and with it the whole application.

Join
We can use the Join method in order to wait for the thread to end:
--------------------------------------------
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(A.f2));
A a = new A();
Thread t2 = new Thread(new ThreadStart(a.f1));
t1.Start();
t2.Start();
t1.Join(); // Don't continue until t1 is done
Console.WriteLine("Main End");
}
--------------------------------------------
We can pass an timeout parameter to the Join method. This way we can avoid deadlocks. This overload also return the success status of the thread.

Passing Parameters
When we wish to pass a parameter to a thread we need to add a variable which is shared by the caller and the thread:
--------------------------------------------
class A
{
public int Delay = 500;
public void f1()
{
for (int i=0; i<10; i++)
{
Console.WriteLine("f1: " + i);
Thread.Sleep(Delay);
}
}
}
class Class1
{
static void Main(string[] args)
{
A a = new A();
a.Delay = 1000;
Thread t2 = new Thread(new ThreadStart(a.f1));
t2.Start();
Console.WriteLine("Main End");
}
}
--------------------------------------------
In the example above we passed the Delay variable to the thread. We can use any variable that is acessible to the thread in order to pass parameters to the thread.

Changing Thread State
The Abort method will recommend for the thread to stop by throwing an exception in the thread.
--------------------------------------------
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(A.f2));
A a = new A();
a.Delay = 1000;
Thread t2 = new Thread(new ThreadStart(a.f1));
t1.Start();
t2.Start();
Thread.Sleep(1000);
t1.Abort();
Console.WriteLine("Main End");
}
--------------------------------------------
Only the t2 thread will finish gracefully.

The Interrupt method, similar to the Abort method, throws an exception to the thread. The only difference is that after the thread handles the exception it continues to work.

In both cases the exception is asynchronous (you cannot deterministically determin the exact time the thread will catch the exception). You can pass an object (StateInfo) to the exception, and the thread can use it if necessary. Here is how you catch the event in the thread:
--------------------------------------------
public void f1()
{
try
{
// Do stuff here
}
catch (ThreadAbortException ex)
{
// you can use ex.ExceptionState in order to get the object passed in the Abort method.
}
finally
{
}
}
-----------------------------------------------------
If in the catch clause we write Thread.ResetAbort() we can bypass the Abort command and cancel the abort (in Interrupt we have it automatically).

It is not recommended to stop a thread by using Abort if the thread handles resources. The problem is that the exception can be caught at any time, and if it were cought in the finally clause, the exception will not be handled at all, and the thread will be stopped without freeing the resources.

A solution to this problem could be to use a public volatile member flag in the object, which is checked by the thread, and when set to true, the thread exits gracefully:
---------------------------------------
class A
{
public volatile bool ThreadStop = false;
public int Delay = 500;
public void f1()
{
for (int i=0; i<10; i++)
{
if (ThreadStop)
return;
Console.WriteLine("f1: " + i);
Thread.Sleep(Delay);
}
}
}

class Class1
{
static void Main(string[] args)
{
A a = new A();
a.Delay = 1000;
Thread t2 = new Thread(new ThreadStart(a.f1));
t2.Start();
Thread.Sleep(1000);
a.ThreadStop = true;
Console.WriteLine("Main End");
}
}
----------------------------------------
We use a volatile member in order to instruct the garbage colector not to do any optimizations on this member, and to pass it to a register. This will make sure that a concurrent method that wants to stop the thread will not have a context switch problem when registered are restored.

Suspend and Resume can help external threads to control the state execution status.

Appartment State
If you want call a COM component from the thread you should know whether it runs in a Multi Threaded Appartment (MTA) or Single Stated Apartment (STA). The Thread class has an ApartmentState property, that you can set to MTA (default) or STA. If the COM and the thread appartment state do not match you might get performance issues (because of extra marshaling between apartments).
You might have noticed that the automated Main method has an attribute called [STAThread]. This means that the main thread will always work with STA appartments. The question that comes to mind is why not just write "Thread.CurrentThread.ApartmentState = ApartmentState.STA;" in the first line of Main. The answer is that the Main is not the first command the application does. There are many operations that happen when the application loads before Main is called, and in these operations there might have been calls to COM.

Summary
In this article I introduced some classes in .NET that represents units of run-time environment in the CLR and in Win32. I covered some of the capabilities of the Thread class. I showed how to run threads in the background, how to wait for a thread to end, how to make a thread sleep, and how to tell a thread to end.
In the next article I will discuss other issues regarding threads.

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.