I want to do some stuff in my app that is time consuming but which does not have an impact on the actual game. Therefore I am trying to use a thread to run that stuff separately. Here is how I create my thread:
Thread thread = new Thread(() => WriteCheckpointTextFile (myFileName, checkpoint));
thread.Start();
Here is the code I am running in WriteCheckpointTextFile:
void WriteCheckpointTextFile (string fileName, Checkpoint checkpoint)
{
string checkpointToText = "";
checkpointToText += CheckpointDataNames.nextMarker + Environment.NewLine;
checkpointToText += checkpoint.nextMarker + Environment.NewLine;
checkpointToText += "%" + Environment.NewLine;
checkpointToText += CheckpointDataNames.difficulty + Environment.NewLine;
checkpointToText += checkpoint.difficulty.ToString () + Environment.NewLine;
checkpointToText += "%" + Environment.NewLine;
checkpointToText += CheckpointDataNames.musicObject + Environment.NewLine;
checkpointToText += checkpoint.musicObject + Environment.NewLine;
checkpointToText += "%" + Environment.NewLine;
checkpointToText += CheckpointDataNames.gameTimePassed + Environment.NewLine;
checkpointToText += checkpoint.gameTimePassed.ToString () + Environment.NewLine;
checkpointToText += "%" + Environment.NewLine;
checkpointToText += CheckpointDataNames.storedStats + Environment.NewLine;
checkpointToText += EndRaceDataString (checkpoint.storedStats);
checkpointToText += "%" + Environment.NewLine;
checkpointToText += CheckpointDataNames.pursuers + Environment.NewLine;
foreach (PursuerData p in checkpoint.pursuers) {
checkpointToText += p.pursuerName + "," + p.pursuerNextMarker + "," + p.pursuerPosition.x.ToString () + "," + p.pursuerPosition.y.ToString () + "," + p.pursuerPosition.z.ToString () + Environment.NewLine;
}
checkpointToText += "%" + Environment.NewLine;
#if !UNITY_WEBPLAYER
System.IO.File.WriteAllText (fileName, checkpointToText);
#endif
Debug.Log("Finished writing text to file");
}
Now for some reason that last debug doesn't always get printed and my file is not always written. What could be causing this?
The issue is that your collection checkpoint.storedStats
is being modified from another thread (likely you're main thread) while you're iterating on it in your background thread. This throws an InvalidOperationException
. Because your code is executing on a background thread, the exception is being silently thrown, unhandled, and ignored by the Unity player/engine.
Unfortunately, this means you'll have to synchronize or restrict access and changes to your checkpoint.storedStats
collection so that only 1 thread may make modifications to it and while doing so, other threads must not be iterating on it.
It's difficult to comment on the best or simplest method for you to implement because I don't know how you use the storedStats
collection throughout your application. One possible, and fairly typical solution would be to wrap all places access the collection with a lock
statement. This will ensure that only 1 thread enters the locked block(s) at any given time. Then when accessing the storedStats
for reading (not writing) you can provide a method on your GameObject's script to lock
the collection, make a copy of it, and return that copy. Since it's a copy, you can safely iterate on it:
public class Checkpoint : MonoBehaviour
{
//used for locking
private readonly object ThreadLocker = new Object();
private List<Stat> storedStats = new List<Stat>();
public List<State> GetStoredStatsCopy()
{
lock(ThreadLocker)
{
//makes a copy/snapshot of the list
return new List<Stat>(storedStats);
}
}
//sample code that changes storedStats
public void AddStat(Stat newStat)
{
lock(ThreadLocker)
{
storedStats.Add(newStat);
}
}
}
Notice that the Checkpoint
script now makes storedStats
private. I've also added some sample method that modifies or writes to that underlying storedStats
. So all access to that storedStats
is controlled and threads are synchronized. This way your background thread can't be iterating on the storedStats
collection (because it has a copy, and the creation of the copy is controlled/thread-safe) and any writes to the collection cannot occur at the same time as another thread is accessing it.
EDIT: And with the above API change, your calling code might look like:
checkpointToText += EndRaceDataString (checkpoint.GetStoredStatsCopy());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With