为什么前后创建的两个任务产生相同的随机值?

本文关键字:任务 随机 创建 为什么 两个 | 更新日期: 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) {
}

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