Tuesday, 3 July 2012

Generic XNA - Threading for Windows



Well I have done some basic threading in my job, basic stuff and thought I would try and put the basic threading I have done into an XNA Game Component. I am not saying this is how you should do your threading, my method is certainly not the only way to thread processes either and more than likely not the best. I think though that if you are new to development and you want to thread part of your game then I think this may be of some use to you.

What is a thread?

OK, there are a few links out there that describe what a thread is better than I could, so here are a few links for reference:-
Research Microsoft Andrew D. Birrel (not read this yet, looks v good though)
MSDN (Again, I should read this too...)

ThreadManager

This is a simple manager, you can pass methods to it that you want to have processed in a thread of there own. You can have as many threads as you like, BUT, it's probably best to keep your threads down to as few as possible, don't go threading processes because you can.

So, on to the code. The ThreadManager class is derived from (as most my samples) from GameComponent. The threading method I use requires four components, ParameterizedThreadStart if you don't need to pass parameters to your thread start method then you can use ThreadStart; these are how we bind our method to a Thread, Thread this is the actual thread class used to start and stop the thread, ManualResetEvent this class is used to manage the call interval for the thread and the Mutex class which is used to ensure there are no thread clashes. I wanted to be able to add as many threads as I wanted in my ThreadManager so I have put these class instances into Dictionaries. Also in this implementation I wanted to be able to manage how often the thread code was ran and also to be able to kill a thread at will. To do this I added two other dictionaries to hold what threads I want to stop and one for the interval between thread calls.

/// <summary>
/// This is the Thread manager.
/// </summary>
public class ThreadManager : GameComponent
{
/// <summary>
/// List of ParameterizedThreadStart used to start the treads.
/// </summary>
private Dictionary<int, ParameterizedThreadStart> threadStarters = new Dictionary<int, ParameterizedThreadStart>();
/// <summary>
/// List of runnign threads.
/// </summary>
private Dictionary<int, Thread> threads = new Dictionary<int, Thread>();
/// <summary>
/// Used to make the thread wait.
/// </summary>
public static Dictionary<int, ManualResetEvent> threadStopEvent = new Dictionary<int, ManualResetEvent>();
/// <summary>
/// Mutex's to stop thread clashes.
/// </summary>
public static Dictionary<int, Mutex> mutexs = new Dictionary<int, Mutex>();
/// <summary>
/// List of bools to control imediate stopping of thread loops.
/// </summary>
public static Dictionary<int, bool> stopThreads = new Dictionary<int, bool>();
/// <summary>
/// List of intervals threads will wait befoer next cycle.
/// </summary>
public static Dictionary<int, int> threadIntervals = new Dictionary<int, int>();
/// <summary>
/// Managers GameTime to be passed onto the threads.
/// </summary>
static GameTime gameTime;

/// <summary>
/// ctor
/// </summary>
/// <param name="game">Calling game class</param>
public ThreadManager(Game game)
: base(game)
{ }

/// <summary>
/// Overiden Update call, loads manager gameTime varaible.
/// </summary>
/// <param name="gameTime"></param>
public override void Update(GameTime gameTime)
{
ThreadManager.gameTime = gameTime;

base.Update(gameTime);
}

/// <summary>
/// Method to add a thread to the maanger.
/// </summary>
/// <param name="threadCode">Code to be executed in the thread.</param>
/// <param name="threadInterval">Time period between each call in miliseconds</param>
/// <returns>Index of thread, first one added will be 0 next 1 etc..</returns>
public int AddThread(ThreadCode threadCode, int threadInterval)
{
int retVal = threads.Count;

threadStarters.Add(threadStarters.Count, new ParameterizedThreadStart(ThreadWorker));
threads.Add(threads.Count, new Thread(threadStarters[threads.Count]));
threadStopEvent.Add(threadStopEvent.Count, new ManualResetEvent(false));
mutexs.Add(mutexs.Count, new Mutex());
threadIntervals.Add(threadIntervals.Count, threadInterval);
stopThreads.Add(stopThreads.Count, false);

threads[threads.Count - 1].Start(new ThreadCodeObj(threadCode, threads.Count - 1));

return retVal;
}

/// <summary>
/// Method to kill a single thread.
/// </summary>
/// <param name="index"></param>
public void KillThread(int index)
{
mutexs[index].WaitOne();
stopThreads[index] = true;
threads[index].Join(0);
mutexs[index].ReleaseMutex();
}

/// <summary>
/// Method to start a thread
/// </summary>
/// <param name="threadCode"></param>
/// <param name="threadInterval"></param>
/// <param name="index"></param>
public void StartThread(ThreadCode threadCode, int threadInterval, int index)
{
if (threads[index].ThreadState == ThreadState.Stopped)
{
threads[index] = new Thread(threadStarters[index]);
threadIntervals[index] = threadInterval;
stopThreads[index] = false;
threads[index].Start(new ThreadCodeObj(threadCode, index));
}
}

/// <summary>
/// This is the method that is called per thread.
/// </summary>
/// <param name="ThreadCodeObject">Instance of ThreadCodeObj to execute.</param>
private static void ThreadWorker(object ThreadCodeObject)
{
int index = ((ThreadCodeObj)ThreadCodeObject).threadIndex;

do
{
try
{
mutexs[index].WaitOne();
if (gameTime != null)
((ThreadCodeObj)ThreadCodeObject).CodeToCall(gameTime);

}
finally
{
mutexs[index].ReleaseMutex();
}
}
while (!threadStopEvent[index].WaitOne(threadIntervals[index], false) && !stopThreads[index]);
}

/// <summary>
/// Method to tidy up unfinished threads.
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
for (int t = 0; t < threads.Count; t++)
KillThread(t);

base.Dispose(disposing);
}
}

As you can see in the code snippet above, there are 4 methods that are not native to the GameComponent, AddThread, KillThread, StartThread, ThreadWorker.

AddThread

Well, guess what this method does...yep, adds a thread to the manager. So each thread needs one of the base thread elements to, I also set the interval and if the thread is stopped. I set the flag to say this thread has been stopped, then call the threads Join method, this stops (blocks) this thread from running until the thread terminates, this won't happen until the threads loop in ThreadWorker exits and is why I set the stop flag for the thread so that it will terminate. You will see in this call I use another class ThreadCodeObj.

ThreadCodeObj
/// <summary>
/// This is the delegate to be used for passing the code to be called in the tread.
/// </summary>
/// <param name="gameTime">GameTime</param>
public delegate void ThreadCode(GameTime gameTime);

/// <summary>
/// This class holds the required data for the code to be called in the thread.
/// </summary>
internal class ThreadCodeObj
{
public ThreadCode CodeToCall = null;
public int threadIndex = -1;

public ThreadCodeObj(ThreadCode code, int index)
{
CodeToCall = code;
threadIndex = index;
}
}

This class stores the code to be called in the thread and the index of the thread it is to be used in.

KIllThread

Yep, you got it, kills a running thread. First off the treads mutex calls it's WaitOne method, this method holds this thread up until it's free to do it's work.

StartThread

Restarts a killed thread. First check if this thread has been stopped, if it has then re initialise the threads elements.

ThreadWorker

Well this I guess is where it all happens, first we get the index of the thread we are in, then we kick off the threads loop, get the mutex for this thread to tell us when it is safe to call the delegate, then we make sure the gameTime instance is not null and call the delegate. We then get the mutex for this thread to signal that we are done and then check if we need to leave the loop and if not wait for the given interval for this thread index.

Implementation

So as with most of my samples (I like to think) it's simple to add a method to a thread. First of all set up the ThreadManager and add it to the Game.Components list, now simply call the ThreadManager.AddThread method, passing it the method you want to put in the thread via the ThreadCode delegate along with the interval you want for the thread. I guess most of the threads you will create will have a time span of 0 so they are executed immediately.

You will also notice I have not bothered with affinity (the ability to request the processor the thread will run on) in this sample, this is because on a PC, you wont know how many CPU's your application will be running on so it's probably best left up to the OS to sort it out.

My next post on threading will be for the XBox, it will be almost identical with this source but will have affinity included, we know where we stand with the 360, we know what processors, how many cores and which ones we can put threads on.

The sample code shows this working in a very simplistic manor, I simply add three methods to the thread manager that populate a variable that holds the game time each time it is called. You can kill the thread and then re start them with an interval of 0.

Source solution can be found here.

No comments:

Post a Comment