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);
}
}
/// 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 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