为什么前后创建的两个任务产生相同的随机值?
本文关键字:任务 随机 创建 为什么 两个 | 更新日期: 2023-09-27 18:11:45
Task.Factory.StartNew(() =>
{
new Class1();
})
Task.Factory.StartNew(() =>
{
new Class2();
})
在class1和class2的构造函数中:
var timeout = new Random().Next(0, 5000);
Debug.Print(timeout.ToString());
两个类中的随机值'timeout'总是相同的。我不明白为什么……
如果我在创建任务之间添加一个暂停,那么它就不一样了。
编辑:我不明白这与"随机字符串生成器返回相同字符串"有什么关系。
他们正在方法中创建随机实例。我在完全不同的任务中调用它,所以它们应该是相互独立的
我不明白这与"随机字符串生成器返回相同字符串"有什么关系。
没有直接关系,尽管根本原因是一样的。一个更好的问题是:为什么在这段代码中我总是得到两个相同的随机值?
它包含了new Random
所做的事情的解释-提供给文档:
默认种子值来自系统时钟,具有有限的分辨率。因此,通过调用默认构造函数连续创建的不同Random对象将具有相同的默认种子值,因此将产生相同的随机数集。
换句话说:如果您快速连续创建Random
对象,它们将产生相同的随机数序列。
他正在方法中创建随机实例。我在完全不同的任务中调用它,所以它们应该是相互独立的。
这些对象是否在不同的线程(或Task
)中创建是无关紧要的-它们只依赖于创建时的系统时间,而不依赖于其他。它们实际上是相互独立的,就像你说的。但是它们都依赖于相同的种子值,即创建时的系统时间。
解决这个的正确方法通常是只有一个Random
类实例。-事实上,像这样的代码:new Random().Next(…)
是代码气味,因为它误用了Random
类:你不应该为每次调用生成一个新的实例;相反,您应该重用相同的实例来生成一个由随机数组成的序列。
不幸的是,您不能在不同的并发任务中简单地使用相同的Random
实例,因为相关的方法不是线程安全的——也就是说,从多个线程并发调用它可能会导致竞争条件。有几种方法可以解决这个问题,但最简单的方法是使用显式锁:
public Class(Random rng) {
lock (rng) {
var timeout = rng.Next(5000);
Debug.Print(timeout.ToString());
}
}
重要的是要注意,对rng
的每个访问必须被锁定,否则这是没有意义的。
现在你可以创建你的任务并运行它们,并获得适当的随机性:
var rng = new Random();
var tasks = new [] {
Task.Run(() => { new Class(rng); }),
Task.Run(() => { new Class(rng); })
};
Task.WaitAll(tasks);
注意,当省略lock(…)
块时,它可能看起来就像您得到了正确的结果。这就是处理并发性和随机性的危险之处:很难验证您的结果是否真正正确,或者在此过程中是否损坏了。所以小心行事
无参数Random
类构造函数使用与时间相关的方法确定随机数生成算法的初始种子。
public Random()
: this(Environment.TickCount) {
}
这就是为什么当你同时创建实例时,它们会产生相同的结果。