为什么在彼此之后创建的两个任务会生成相同的随机值?

时间:2014-07-06 22:34:27

标签: c# random

Task.Factory.StartNew(() =>
    {
    new Class1();
    })

Task.Factory.StartNew(() =>
    {
    new Class2();
    })

在class1和class2的构造函数中,我有:

var timeout = new Random().Next(0, 5000);
Debug.Print(timeout.ToString());

随机值'超时'两个班级总是一样的。我不明白为什么......

如果我在创建任务之间添加暂停,那么它就不一样了。

修改

我不明白这与" Random String Generator Returning Same String"有什么关系。

他正在该方法中创建随机实例。我在完全不同的任务中调用它,所以它们应该彼此独立。

2 个答案:

答案 0 :(得分:52)

  

我不明白这与"随机字符串生成器返回相同的字符串"。

它没有直接关系,虽然根本原因是相同的。更好的重复是这个问题:Why do I keep getting two of same random values in this code?

它包含new Random所做内容的an explanation - documentation的礼貌:

  

默认种子值源自系统时钟并具有有限的分辨率。因此,通过调用默认构造函数紧密连续创建的不同Random对象将具有相同的默认种子值,因此将生成相同的随机数集。

换句话说:如果您快速连续创建Random个对象,它们将生成相同的随机数序列。

  

他正在该方法中创建随机实例。我在完全不同的任务中调用它,所以它们应该彼此独立。

这些对象是否在不同的线程(或Task s)中创建是无关紧要的 - 它们仅依赖于创建它们时的系统时间,而不依赖于其他任何线程。他们 实际上是彼此独立的,就像你说的那样。但它们都依赖于相同的种子值,即创建时的系统时间。


解决此问题的正确方法通常只有Random类的一个实例。 - 事实上,像这样的代码:new Random().Next(…)是代码味道,因为它错误地使用了Random类:你不应该为每个调用生成一个新实例;相反,您应该重复使用相同的实例来生成随机数的序列

不幸的是,你不能简单地在不同的并发任务中使用相同的Random实例,因为relevant method不是线程安全的 - 也就是说,同时从多个线程调用它可能会导致竞争条件。有几种解决方法,但最简单的方法是使用显式锁:

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(…)块时,它可能似乎就像您获得了正确的结果一样。这是使用并发性和随机性的危险:很难验证您的结果是否真正正确或是否在整个过程中被破坏。所以谨慎行事。

答案 1 :(得分:12)

无参数Random类构造函数使用与时间相关的方法来确定随机数生成算法的初始种子。

public Random() 
  : this(Environment.TickCount) {
}

这就是为什么当你同时创建实例时它们会产生相同的结果。