所以你在这里有几个问题。
首先,为什么您两次看到相同的值。那是最容易的。当您创建一个Random
实例时,它会以当前时间为种子,但它使用的当前时间的精度相当低。如果您Random
在 16 毫秒左右(这对于计算机来说是一个非常长的时间)内获得了两个新实例,您会从它们中看到相同的值。这就是你正在发生的事情。
通常解决方法只是共享一个Random
实例,但问题是您的随机实例没有从同一个线程访问(可能,假设您没有SynchronizationContext
指定),并且Random
不是线程安全的. 您可以使用类似这样的方法来获取随机数:
public static class MyRandom
{
private static object key = new object();
private static Random random = new Random();
public static int Next()
{
lock (key)
{
return random.Next();
}
}
//TODO add other methods for other `Random` methods as needed
}
使用它,它将解决眼前的问题。
您遇到的另一个问题,尽管目前似乎并没有让您感到困扰,但您正在List
从两个不同的任务中修改您的任务,可能在不同的线程中执行。你不应该那样做。在单线程环境中使用这样的方法已经够糟糕的做法了(因为您依赖副作用来完成工作),但在多线程环境中,这非常有问题,不仅仅是概念上的原因。相反,您应该让每个线程返回一个值,然后将所有这些值拉入调用方的集合中,如下所示:
public async Task HelloTest()
{
var data = await Task.WhenAll(Say(), Say());
}
private static async Task<int> Say()
{
await Task.Delay(100);
return MyRandom.Next();
}
至于为什么这两个Say
调用是并行运行而不是顺序运行的,这与在您的第二个代码片段中您实际上并没有在开始下一个任务之前等待一个任务完成的事实有关。
您传递给Select
的方法是启动任务的方法,它不会阻塞,直到该任务完成后再开始下一个任务。你在这里的代码:
await Task.WhenAll(Enumerable.Range(0, 2).Select(async x => await Say(hello)));
与简单地拥有没有什么不同:
await Task.WhenAll(Enumerable.Range(0, 2).Select(x => Say(hello)));
拥有一个async
除了等待一个方法调用之外什么都不做的方法与只拥有一个方法调用没有什么不同。这里发生的Select
是调用Say
,盯着任务,继续,统计下一个任务,然后WhenAll
(异步)等待两个任务完成,然后继续。