Unity3d和线程

kUr*_*4m4 5 c# io multithreading unity-game-engine

我想在我的应用程序中做一些耗时但对实际游戏没有影响的东西.因此,我试图使用一个线程分别运行这些东西.以下是我创建线程的方法:

Thread thread = new Thread(() => WriteCheckpointTextFile (myFileName, checkpoint));
thread.Start();
Run Code Online (Sandbox Code Playgroud)

这是我在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");
}
Run Code Online (Sandbox Code Playgroud)

现在出于某种原因,上次调试并不总是打印出来,并且我的文件并不总是被写入.可能是什么导致了这个?

Chr*_*air 3

问题是,当checkpoint.storedStats您在后台线程中迭代集合时,另一个线程(可能是主线程)正在修改您的集合。这会抛出一个InvalidOperationException. 由于您的代码在后台线程上执行,因此 Unity 播放器/引擎会默默地抛出、未处理和忽略异常。

不幸的是,这意味着您必须同步或限制对checkpoint.storedStats集合的访问和更改,以便只有 1 个线程可以对其进行修改,并且在执行此操作时,其他线程不得对其进行迭代。

很难评论您实现的最佳或最简单的方法,因为我不知道您如何storedStats在整个应用程序中使用该集合。一种可能且相当典型的解决方案是用一条语句包装所有访问集合的lock位置。这将确保在任何给定时间只有 1 个线程进入锁定块。然后,当访问storedStats用于读取(而不是写入)时,您可以在游戏对象的脚本上向集合提供一个方法lock,制作它的副本,然后返回该副本。由于它是副本,因此您可以安全地对其进行迭代:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,该Checkpoint脚本现在将 设为storedStats private。我还添加了一些示例方法来修改写入该底层storedStats. 因此,对它的所有storedStats访问都受到控制,并且线程都是同步的。这样,您的后台线程就无法在storedStats集合上迭代(因为它有一个副本,并且副本的创建是受控/线程安全的),并且对集合的任何写入都不能在另一个线程访问的同时发生它。

编辑:通过上述 API 更改,您的调用代码可能如下所示:

checkpointToText += EndRaceDataString (checkpoint.GetStoredStatsCopy());
Run Code Online (Sandbox Code Playgroud)