c# - 为什么 System.Random.Sample 会抛出 IndexOutOfRangeException?
问题描述
我在 Unity (2017.1.1f1) 中调用Next()
了一个 .NET实例。它从受保护的函数Sample()System.Random
内部抛出一个,它不接受任何参数并返回 0 和 1 之间的双精度值。什么可能导致这种行为?IndexOutOfRangeException
这是详细的例外情况
System.IndexOutOfRangeException:数组索引超出范围。
在 /Users/builduser/buildslave/mono/build/mcs/class/corlib/System/Random.cs:91 中的 System.Random.Sample () [0x0003e]
在 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 rand,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()。
编辑
以下是 Random 实例的创建和分发方式:
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;
}
解决方案
唯一有意义的解释是您认为您正在访问Random()
实例线程安全,用您自己的话来说,每个线程都有自己的Random()
实例,但看起来您正在访问同一个Random()
实例,而它仍在计算中间。这是 Unity 中的实现。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
是有限的,即当您访问时this.SeedArray
。这是 SeedArray 的定义。
public class Random
{
private int[] SeedArray = new int[56];
....
}
它已经分配给 56 个元素,在InternalSample
方法的实现中,您可以看到对于每次调用index1
,index2
并且始终限制为最多55
,除非InternalSample
方法被多次调用。
推荐阅读
- json - Groovy:将值从 json 添加到地图
- java - 创建帐户按钮总是会在浏览器中登录 gmail 帐户。它怎么会总是向谷歌征求许可?
- kubernetes - K8s 容错
- java - 在这段代码中,我得到了无法解析符号 R
- c++ - C++ 中的快速 I/O
- css - 如何根据用户登录和注销加载样式
- reinforcement-learning - 简而言之,强化学习中的策略梯度算法中的目标网络是什么?
- java - maven 上的线程“main”java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper 中的异常
- ios - SwiftUI 关闭导航项
- java - HTTP POST 方法说在 Android 上找不到 404 文件异常