【问题标题】:Unity3d and ThreadingUnity3d 和线程
【发布时间】:2014-06-11 03:10:50
【问题描述】:

我想在我的应用中做一些耗时但对实际游戏没有影响的事情。因此我试图使用一个线程来单独运行这些东西。以下是我创建线程的方式:

Thread thread = new Thread(() => WriteCheckpointTextFile (myFileName, checkpoint));
thread.Start();

这是我在 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");
}

现在由于某种原因,最后一次调试并不总是被打印出来,而且我的文件并不总是被写入。这可能是什么原因造成的?

【问题讨论】:

  • 你总是可以添加更多的调试打印来查看它到底在哪里停止。尝试将 WriteAllText 方法包装在 try-catch 中并记录异常。另外,如果您使用 async 和 await,您的代码表现如何?
  • 您的进程是终止还是不打印到调试窗口?
  • 我将添加更多调试以找出它停止的确切位置。据我所知,writealltext 没有引发任何异常,但让我回复您。
  • @kUr4m4:另外,checkpoint.storedStats 的类型是什么?我敢打赌它是一个集合(比如List<Stat>),你在EndRaceDataString 方法中用foreach(var stat in storedStats) 循环迭代它,它抛出一个InvalidOperationException 说“集合已修改;枚举操作可能不会执行”
  • 哇,你会赢的!这是一个例外:集合被修改;枚举操作可能无法执行。

标签: c# multithreading io unity3d


【解决方案1】:

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

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

很难评论您实施的最佳或最简单的方法,因为我不知道您如何在整个应用程序中使用storedStats 集合。一种可能且相当典型的解决方案是使用lock statement 包装访问集合的所有位置。这将确保在任何给定时间只有 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);
        }
    }
}

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

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

checkpointToText += EndRaceDataString (checkpoint.GetStoredStatsCopy());

【讨论】:

  • 太棒了,我将尝试实施您的解决方案。我认为制作storedStats 的副本应该是我所需要的,因为我实际上只关心存储数据时的数据。以后添加的任何内容,我都不在乎,因为它会在检查点之后生成。我会尽快将其标记为正确的:)非常感谢您的帮助!
  • @kUr4m4:不幸的是,即使您正在制作副本,您仍然需要锁定:制作副本的行为可能会引发相同的错误(因为它需要迭代storedStats 集合来制作副本)如果在制作副本时修改了集合。
  • 好的,我也会看看锁,但我已经看到,在我的自定义类中复制每个列表似乎消除了抛出的异常(我猜只是运气事实上,复制它的过程比我对列表所做的要快得多)。由于您已检测到问题并提供了足够充分的解决方案,因此我会将解决方案标记为正确!再次感谢您!
猜你喜欢
  • 2014-08-27
  • 1970-01-01
  • 2017-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多