为什么System.Random.Sample抛出IndexOutOfRangeException?

时间:2018-10-04 02:12:24

标签: c# unity3d

我正在Unity(2017.1.1f1)内的.NET Next()实例上调用System.Random。它从受保护的函数Sample()中抛出一个IndexOutOfRangeException,该函数不接受任何参数,并返回0到1之间的双精度值。

这里是例外情况

  

System.IndexOutOfRangeException:数组索引超出范围。

     

在System.Random.Sample()[0x0003e]中   /Users/builduser/buildslave/mono/build/mcs/class/corlib/System/Random.cs:91

     在System.Random.Next(Int32 maxValue)[0x00017]中的

  /Users/builduser/buildslave/mono/build/mcs/class/corlib/System/Random.cs:112

     在Quicksilver.SysIRand.RandInt(Int32 max_exclusive)[0x00008]中位于

  F:\ SVNHome \ gemrush \ GemRush \ Assets \ Source \ Shared \ Utility \ IRand.cs:38

     在Quicksilver.IEnumerableExt.SelectRandom [Skill](IEnumerable`1   集合,IRand兰特,Int32计数)[0x00070]在   F:\ SVNHome \ gemrush \ GemRush \ Assets \ Source \ Shared \ Utility \ IEnumerableExt.cs:61

(在此之上还有13层调用栈)

这是一个多线程环境,但是每个线程都有其自己的System.Random专用实例。从下面的代码中可以看到,传递给Next()的参数必须为1或更高。

此错误在复杂的自动化测试脚本中投入了大约1个小时,因此多次运行以测试理论是昂贵的,并且任何更改RNG调用方式的修改都将阻止复制。 (如果错误某种程度上涉及线程之间的意外交互,则可能根本无法再现。)

由于它已经进入测试脚本一个小时了,因此该方法的绝大多数调用都不得抛出错误。

直接使用随机数的函数在这里:

    // Chooses count items at random from the enumeration and returns them in an array
    // The order of selected items within the array is also random
    // If the collection is smaller than count, the entire collection is returned (in random order)
    public static T[] SelectRandom<T>(this IEnumerable<T> collection, IRand rand, int count = 1)
    {
        if (count <= 0) return new T[0];    // Optimization for trivial case

        T[] keep = new T[count];
        int found = 0;
        foreach (T item in collection)
        {
            if (found < count)
            {
                // Take the first #count items, in case that's all there are

                // Move a random item of those found so far (including the new one)
                // to the end of the array, and insert the new one in its place
                int r = rand.RandInt(found + 1);
                keep[found++] = keep[r];
                keep[r] = item;
            }
            else
            {
                // Random chance to replace one of our previously-selected elements
                ++found;
                if (rand.RandInt(found) < count)    // probability desired/found
                {
                    // Replace a random previously-selected element
                    int r = rand.RandInt(count);
                    keep[r] = item;
                }
            }
        }
        if (found < count)
        {
            // The collection was too small to get everything requested;
            // Make a new, smaller array containing everything in the collection
            T[] all = new T[found];
            Array.Copy(keep, all, found);
            return all;
        }
        return keep;
    }

此行抛出错误:

                if (rand.RandInt(found) < count)    // probability desired/found

IRand是围绕System.Random的非常薄的包装程序的接口; IRand.RandInt()仅返回Random.Next()。

编辑

以下是创建和分发随机实例的方式:

   private void Start()
    {
        SysIRand[] rngs = new SysIRand[parallelTesters];
        if (parallelTesters > 0) rngs[0] = new SysIRand(new System.Random(548913));
        if (parallelTesters > 1) rngs[1] = new SysIRand(new System.Random(138498));
        if (parallelTesters > 2) rngs[2] = new SysIRand(new System.Random(976336));
        if (parallelTesters > 3) rngs[3] = new SysIRand(new System.Random(793461));
        if (parallelTesters > 4) rngs[4] = new SysIRand(new System.Random(648791));
        if (parallelTesters > 5) rngs[5] = new SysIRand(new System.Random(348916));
        if (parallelTesters > 6) rngs[6] = new SysIRand(new System.Random(8467168));
        if (parallelTesters > 7) rngs[7] = new SysIRand(new System.Random(6183569));
        for (int i = 8; i < parallelTesters; ++i)
        {
            rngs[i] = new SysIRand(new System.Random(7 * i * i + 8961 * i + 129786));
        }

        for (int t = 0; t < parallelTesters; ++t)
        {
            SysIRand rand = rngs[t];

            IBot bot = BotFactory.DrawBot(rand);

            BotTester tester = new BotTester(rand, bot);
            tester.testerID = t + 1;
            tester.OnMessage += (str) => UponMessage(tester.testerID + " ~ " + str);
            tester.OnError += (str) => UponError(tester.testerID + " ~ " + str);
            tester.OnGameAborted += UponGameAborted;
            tester.OnDebugMoment += UponDebugMoment;

            testers.Add(tester);
        }

(在此运行中,parallelTesters的值为3)

每个BotTester仅使用传递给其构造函数的Random实例。每个线程对一个单独的BotTester都是私有的,从BotTester.RunGame()开始:

    public bool RunGame(GameMode mode, int maxGames = 1, long maxMilliSeconds = 100000000, int maxRetries = 5000)
    {
        if (threadRunning) return false;
        thread = new Thread(() => ThreadedRunGame(mode, maxGames, maxMilliSeconds, maxRetries));
        thread.Start();
        return true;
    }

1 个答案:

答案 0 :(得分:2)

唯一有意义的解释是,您认为自己访问Random()实例线程安全,每个线程都有自己的Random()实例,但看起来您正在访问相同的{ {1}}实例仍在计算中间。这是Unity中的实现。 Random()只需调用Sample()

InternalSample()

如您所见,您可以获得private int InternalSample() { int inext = this.inext; int inextp = this.inextp; int index1; if ((index1 = inext + 1) >= 56) index1 = 1; int index2; if ((index2 = inextp + 1) >= 56) index2 = 1; int num = this.SeedArray[index1] - this.SeedArray[index2]; if (num < 0) num += int.MaxValue; this.SeedArray[index1] = num; this.inext = index1; this.inextp = index2; return num; } 的位置是有限的,即当您访问IndexOutOfRangeException时。这是SeedArray的定义。

this.SeedArray

它已经分配给56个元素,在执行public class Random { private int[] SeedArray = new int[56]; .... } 方法时,您可以看到每次调用InternalSampleindex1始终被限制为最多{{1} },除非多次调用index2方法。