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

Dim*_*imo 36 c# random

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

Task.Factory.StartNew(() =>
    {
    new Class2();
    })
Run Code Online (Sandbox Code Playgroud)

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

var timeout = new Random().Next(0, 5000);
Debug.Print(timeout.ToString());
Run Code Online (Sandbox Code Playgroud)

随机值'timeout'在两个类中始终相同.我不明白为什么..

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

编辑:

我不明白这与" 随机字符串生成器返回相同的字符串 "有什么关系.

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

Kon*_*lph 57

我不明白这与"随机字符串生成器返回相同的字符串"有什么关系.

虽然根本原因是相同的,但它没有直接关系.更好的重复是这个问题:为什么我在这段代码中不断得到两个相同的随机值?

它包含一个解释是什么new Random呢-礼貌的文档:

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

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

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

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


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

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

public Class(Random rng) {
    lock (rng) {
        var timeout = rng.Next(5000);
        Debug.Print(timeout.ToString());
    }
}
Run Code Online (Sandbox Code Playgroud)

重要的是要注意每次访问都rng必须锁定,否则这是没有实际意义的.

现在,您可以创建任务并运行它们,并获得适当的随机性:

var rng = new Random();

var tasks = new [] {
    Task.Run(() => { new Class(rng); }),
    Task.Run(() => { new Class(rng); })
};
Task.WaitAll(tasks);
Run Code Online (Sandbox Code Playgroud)

需要注意的是留出了当lock(…)块,它可能看起来像你得到正确的结果.这是使用并发性和随机性的危险:很难验证您的结果是否真正正确或是否在整个过程中被破坏.所以谨慎行事.


Mar*_*zek 12

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

public Random() 
  : this(Environment.TickCount) {
}
Run Code Online (Sandbox Code Playgroud)

这就是为什么当您同时创建实例时,它们将生成相同的结果.