Multithreaded Game Engine in C# with SlimDX

Started by
4 comments, last by SeanO'Connor 12 years, 3 months ago
Good day to all, I find myself having serious problems implementing asynchronous functionality in my C# game engine. Everything was going just fine until I started branching things off into tasks. Now, from what I understand, C# 4.0 (and even more so 5.0 when it arrives) has excellent built in support for ansynchronous programming. However, when searching for examples all I ever see are parallel HTML downloaders and database crunchers. This leads me to ask if the built in threading in C# is designed for something as heavy as a game engine? I am almost certain it is, but I want to be sure.

Secondly, I cannot figure out why my current design is so flawed. I know it is flawed because I keep getting extremely random cryptic exceptions in different places--and they only happen sometimes. I get different results from running directly in VS2010 versus running the actual exe from Windows which is really strange to me. This is my first attempt at asynchronous programming and much of this is still quite new to me.

My design is basically a static class that starts each task. Each task is a while(true) loop that continually does the required work. The threads do not talk to each other at all since I know it is bad practice. Rather, any shared data they need comes from another static class for holding global data. That is really all there is to my design, and I cannot figure out why it has so many errors. I also use windows forms and they appear to not like this design either.

This leads me to my final question (I apologize for this rather lengthy post). I came across this fine document on another thread in this forum: http://software.intel.com/en-us/articles/designing-the-framework-of-a-parallel-game-engine/
Unfortunately, I do not fully understand this method, but it sounds solid. It sounds like it is meant for C++, so I was wondering if it would be worth it to switch from C# and try and implement this design? Or is it just as applicable to C#?

Any help is appreciated, thank you.
Advertisement
In my experience most threading issues come from one thread trying to read data that another thred is in the middle of writing, and therefore seeing that data in an inconsistent state. Two threads trying to write to the same data simultaneously can also leave it in an inconsistent state.

How are you synchronizing your worker threads with the main thread?

I wouldn't recommend switching to C++, unless you know it better than C#, the choice of language isn't the issue here.

You might find http://msdn.microsoft.com/en-us/library/3dasc8as%28v=vs.80%29.aspx helpful.
Thank you for the reply. That link you gave me about the thread pool--doesn't C#'s task library (TPL) already implement a full thread pool in the background? I may be wrong, but I think I remember reading that. I am using those Tasks as opposed to traditional threads.

I am not sure if I am even synchronizing worker threads with the main thread. The main thread starts, at the moment, two independent tasks. Basically, there is a static class that contains Main() and Main creates new Task objects. After that I explicitly call the task's start function which starts each task's while(true) loop. That is literally all I do, so it is safe to say I probably am not even synchronizing with the main thread. Is this bad? I am brand new to threading, and I thought the idea was to let the threads be as asynchronous as possible?

Thanks again for the reply.
Hello Sean,

What you describe seem to be synchronization issues, exactly what Adam described. Also WinForms uses a SynchronizationContext. There is nothing wrong with a design where multiple threads work together via a shared variables, but you do need to synchronize the access to this variable. You can do this with the 'lock'-keyword integrated in the C# language.

One way I do this is:



public class SynchronizedDataHolder<T>
{
public Object SynchronizationObject
{
get;
set;
}

private T _data = default ( T );

public T Data
{
get
{
lock ( SynchronizationObject )
{
return _data;
}
}
set
{
lock ( SynchronizationObject )
{
_data = value;
}
}
}

public SynchronizedDataHolder ( )
{
SynchronizationObject = new Object ( );
}
}


Now you have a synchronized data holder class, which you can get and set once you provided the instance to each worker.

Lets see how this fits in a program:


public class Program
{
public ManualResetEventSlim ExitSignal
{
get;
private set;
}

public SynchronizedDataHolder<Double> DataHolder
{
get;
private set;
}

public Task [ ] Workers
{
get;
private set;
}

private void Worker_Callback ( )
{
while ( !ExitSignal.IsSet )
{
// Retrieve the current value in a synchronized method
Double value = DataHolder.Data;

// Emulate some heavy operation...
Thread.SpinWait ( 1000000 );

// Store the result back in the shared data holder, overwriting the result ignoring any concurrency issues
DataHolder.Data = value + 0.1D;
}
}

public void StartWorkers ( )
{
Workers = Enumerable
.Range ( 0 , 10 )
.Select ( index => Task.Factory.StartNew ( Worker_Callback ) )
.ToArray ( );
}

public void StopWorkers ( )
{
ExitSignal.Set ( );
Task.WaitAll ( Workers );
}

public Program ( )
{
DataHolder = new SynchronizedDataHolder<Double> ( );
ExitSignal = new ManualResetEventSlim ( );
}

public static void Main ( )
{
Program program = new Program ( );
program.StartWorkers ( );
// Sleep for approximately 10 seconds
Thread.Sleep ( 10000 );
program.StopWorkers ( );
}
}


I haven't run this program, as I am writing it from my IPhone.

But this is one way to synchronize data access. This example doesn't show how to deal with concurrency, nor does it display multiple readers and single writer access (use the ReaderWriterLockSlim for that).

I hope this helps a bit further.

Don't switch to C++ for this issue, it won't solve anything. I am using C#, TPL and async/await on daily bases without any problems. Infact I love it!
Also - mentioning this so it doesn't become a problem later - make sure you terminate the threads properly once your application closes. Even if the main window and GUI thread dies, the other threads will keep on running until told to stop, and will hang your process (which can only halt once all its threads are gone). And if they happen to read data which existed only in the context of your main GUI thread, you'll get some nasty errors upon closing your program. What I usually do is reserve memory for an array of booleans (one for each thread), give a pointer to it to the threads, and have them set their own bool to true when and only when they die. Then in my main thread I have a conditional loop which prevents the main thread from terminating until all the whole array is set to true.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

@T_Ice
Thanks for the reply. I should have said I am using the built in ConcurrentCollections for my shared data storage. However, there is a lot of stuff in your code sample that I currently do not do and need to try. Even though I am using a ConcurrentDictionary, I am going to try building my own data holder like the one you posted anyway.

@Bacterius
Thanks for the tip--indeed I did not take into account the proper termination of other threads. I assumed that when the main thread dies all others did too, so you likely saved me some future headaches.

Thanks again to all who responded, very useful replies.

This topic is closed to new replies.

Advertisement