First, we’ll use “standard” way. In our first example, we will just create and fire new thread. It will execute in parallel with main thread:
public class Program
{
public static void Main()
{
Thread someThread = new Thread(SomeThreadMethod);
someThread.Start();
...
}
private static void SomeThreadMethod()
{
...
}
}
In the second example, we will execute the thread and will suspend the main thread until we resume it:
public class Program
{
private static Thread mainThread;
public static void Main()
{
mainThread = Thread.CurrentThread;
Thread someThread = new Thread(SomeThreadMethod);
someThread.Start();
mainThread.Suspend();
...
}
private static void SomeThreadMethod()
{
...
mainThread.Resume();
}
}
Thread class contains some useful properties and methods. For example, using Priority property we can control how the thread should be scheduled compare to the other threads.
The second example contains elements of thread synchronisation. Although, it is still OK to use Suspend()
and Resume()
for synchronising threads, it is recommended not using them. They are causing some problems, especially when things come to sharing resources between threads. .NET Microframework provides bunch of classes to help us avoid this problems.
For example, there is a problem with sharing resources when using those methods. Fortunately, there are several ways to solve this. We will have to look on them separately as they are solving different problems.
At the beginning, we’ll look at a method attribute MethodImpl and more specifically MethodImplOptions enumeration. Let’s say we have a method, called by multiple threads, that is doing some unique work and it have to finish it’s execution before next thread can call it. For this case we can attribute the method like this:
[MethodImpl(MethodImplOptions.Synchronized)]
private void SomeSynchronizedMethod()
{
...
}
All threads trying to access this method will be blocked for the time the method is in use from other thread.
In the event that there are enough scenarios that justify existence of this approach, you can see many ones that this is absolutely inappropriate. A better approach is to lock the resources that methods are sharing. For this there is a class called Monitor. Using this class is easy:
private static SomeThreadMethod()
{
...
try
{
Monitor.Enter(someSharedResource);
...
}
finally
{
Monitor.Exit(someSharedResource);
...
}
...
}
private static SomeOtherThreadMethod()
{
...
try
{
Monitor.Enter(someSharedResource);
...
}
finally
{
Monitor.Exit(someSharedResource);
...
}
...
}
Now we can execute both methods in parallel. When one of them reaches Monitor.Enter()
it gets access to the shared resource and then performs operations with it. At this point, if other methods call Monitor.Enter()
to access same resource, it will be blocked until first method execute Monitor.Exit()
to release the resource. Note that Monitor.Exit()
is in finally block of exception handler. This is best practice; otherwise, if exception occurs within the method, the resource may stay locked forever.
This, you may know, is equivalent to C#’s lock keyword. When compiler meets:
lock(someSharedResource)
{
...
}
it compiles it to:
try
{
Monitor.Enter(someSharedResource);
...
}
finally
{
Monitor.Exit(someSharedResource);
}
The Monitor and Thread classes are part of System.Threading namespace. As I mentioned before, there are few other classes and each one of them provides us with different way to solve our problems. As conclusion of this part, we will have a quick look on how to use Interlocked class and its members. It is simple as this:
public class Program
{
private static int someSharedResource;
...
private static void SomeThreadMethod()
{
Interlocked.Increment(ref someSharedResource);
}
private static void SomeOtherThreadMethod()
{
Interlocked.Increment(ref someSharedResource);
}
}
or
public class SharedResources
{
public static int SomeSharedResource;
}
public class Program
{
private static void SomeThreadMethod()
{
Interlocked.Increment(ref SharedResources.SomeSharedResource);
}
private static void SomeOtherThreadMethod()
{
Interlocked.Decrement(ref SharedResources.SomeSharedResource);
}
}
Exchange() and CompareExchange() provides some interesting possibilities, so spend few moments to read the documentation.
We are not done yet with the subject of multithreading, so be ready for more to come in next part.
In addition to the previously mentioned methods for synchronisation of threads, Microframework provided two classes to do this, using events: AutoResetEvent and ManualResetEvent. Both classes inherit WaitHandle and only differ by the fact that you have to manually reset status when using ManualResetEvent. Let’s see some examples of using those classes:
public class Program
{
private static AutoResetEvent autoResetEvent = new AutoResetEvent(false);
static void Main()
{
Thread someThread = new Thread(SomeMethod);
someThread.Start();
...
autoResetEvent.WaitOne();
...
someThread.Start();
autoResetEvent.WaitOne();
...
}
private static void SomeMethod()
{
...
autoResetEvent.Set();
}
}
or
public class Program
{
private static ManualResetEvent namualResetEvent = new ManualResetEvent(false);
static void Main()
{
Thread someThread = new Thread(SomeMethod);
someThread.Start();
...
namualResetEvent.WaitOne();
...
namualResetEvent.Reset();
someThread.Start();
namualResetEvent.WaitOne();
...
}
private static void SomeMethod()
{
...
namualResetEvent.Set();
}
}
WaitHandle class (thus both AutoResetEvent and ManualResetEvent classes too) have two more interesting and flexible methods – WaitAll() and WaitAny(). First waits for all the elements in the specified array to receive a signal and second waits for any of the elements to receive a signal. Note that they accept array of WaithHandle
s thus way you can create and work with many threads and synchronise between them. Both methods has overrides that allow specifying the number of milliseconds to wait and parameter to specify shall thread(s) to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it afterwards.
You can set the thread priority using ThreadPriority enumeration and check thread status using ThreadState enumeration. TheadState
allows you to perform bitwise operations so you can check for multiple statuses:
if (someThread.ThreadState == (ThreadState.Suspended | ThreadState.Stopped))
{
...
}
Lastly, we must not leave Timer class without attention :) This is very useful class that “Provides a mechanism for executing a method at specified intervals”. It is simple as it states. Timer’s constructor uses TimerCallback delegate for the method you want to execute and there are two handy overrides, which allow us to specify times intervals using int (in milliseconds) or TimeSpan. The second parameter is also useful in case you want to pass additional information to the executed method. Keep it null if no information is to be passed.
public class Program
{
static void Main()
{
var someTimer = new Timer(new TimerCallback(SomeMethod), "Some data", 5000, 1000);
...
Console.ReadLine();
}
private static void SomeMethod(object data)
{
...
Console.WriteLine((string)data);
...
}
}
One good thing about last version Microframework is that we can use lambda expressions:
public class Program
{
static void Main()
{
var someTimer = new Timer(new TimerCallback(
(object data) =>
{
Console.WriteLine((string)data);
}),
"Some data", new TimeSpan(0, 0, 0, 5), new TimeSpan(0, 0, 0, 1));
...
Console.ReadLine();
}
}
Last note to take is that Microframework’s System.Threading namespace does not implement all the classes of the full .NET framework System.Threading.
Now I want to show you one more way to synchronise threads. It is more like “waiting” a thread to finish, before continues with execution of the code in the calling thread. I have seen people usually do this in following manner:
public class Program
{
public static void Main()
{
Thread someThread = new Thread(SomeThreadMethod);
someThread.Start();
Thread.Sleep(Timeout.Infinite);
}
private static void SomeThreadMethod()
{
...
}
}
Well it might look OK and it works OK but it is somehow “ugly” and you lose control over the main thread continuation. .NETMF provides nice way of doing this:
public class Program
{
public static void Main()
{
Thread someThread = new Thread(SomeThreadMethod);
someThread.Start();
someThread.Join();
...
}
private static void SomeThreadMethod()
{
...
}
}
Now that’s better! You still may block the app from exiting: just never return from SomeThreadMethod()
. But anytime you decide to change algorithm you can do it easily. Another advantage of Join()
method is that you do not have to do event synchronising thus saving yourself writing few more lines of code.
Here also one very quick way just to drop some thread to do something:
public class Program
{
public static void Main()
{
new Thread(
() =>
Debug.Print("Hello ...")
) { Priority = ThreadPriority.AboveNormal }
.Start();
new Thread(
() =>
{
Debug.Print("Line 1");
Debug.Print("Line 2");
}) { Priority = ThreadPriority.Lowest }
.Start();
...
}
private static void SomeThreadMethod()
{
...
}
}