代码之家  ›  专栏  ›  技术社区  ›  Jaycee

为什么lambda表达式中的事件顺序是这样的?

  •  -2
  • Jaycee  · 技术社区  · 13 年前

    这是我正在运行的代码:

        [TestMethod]
        public async Task HelloTest()
        {
            List<int> hello = new List<int>();
           //await Task.WhenAll(Enumerable.Range(0, 2).Select(async x => await Say(hello)));
            await Say(hello);
            await Say(hello);
        }
    
        private static async Task Say(List<int> hello)
        {
            await Task.Delay(100);
            var rep = new Random().Next();
            hello.Add(rep);
        }
    

    为什么按原样运行这段代码会产生两个随机数,而使用注释代码却总是产生两个完全相同的数字?

    2 回复  |  直到 9 年前
        1
  •  2
  •   Servy    13 年前

    所以你有几个问题。

    首先,为什么两次看到相同的值。这很简单。当您创建 Random 实例中,它使用当前时间进行播种,但它使用的当前时间的精度相当低。如果你有两个新的 随机的 例如16毫秒左右的实例(这是一个 真正地 长时间使用计算机),你会从中看到相同的值。这就是你正在发生的事情。

    通常情况下,解决方法只是共享一个 随机的 实例,但问题是您的随机实例不是从同一个线程访问的(可能,假设您没有 SynchronizationContext 指定),以及 随机的 线程不安全。你可以用这样的方法来获取你的随机数:

    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 只等待一个方法调用什么都不做的方法实际上和只进行一个方法呼叫没有什么不同。这里发生的事情是 选择 正在呼叫 ,开始任务,继续,统计下一个任务,然后 WhenAll 正在(异步)等待两个任务完成,然后再继续。

        2
  •  0
  •   Matt Smith    13 年前

    注意:这是对 起初的 问题(问题已更改)。

    它们的操作方式相同。我运行了以下控制台应用程序,得到了两个版本的结果0 1:

    class Program
    {
        static int m_nextNumber = 0;
    
        static void Main(string[] args)
        {
            var t1 = Version1();
    
            m_nextNumber = 0;
            var t2 = Version2();
    
            Task.WaitAll(t1, t2);
            Console.ReadKey();
        }
    
        static async Task Version1()
        {
            List<int> hello = new List<int>();
            await Say(hello);
            await Say(hello);
            PrintHello(hello);
        }
    
        static async Task Version2()
        {
            List<int> hello = new List<int>();
            await Task.WhenAll(Enumerable.Range(0, 2).Select(async x => await Say(hello)));
            PrintHello(hello);
        }
    
        static void PrintHello(List<int> hello)
        {
            foreach (var i in hello)
                Console.WriteLine(i);
        }
    
        static int GotANumber()
        {
            return m_nextNumber++;
        }
    
        static async Task Say(List<int> hello)
        {
            var rep = GotANumber();
            hello.Add(rep);
        }
    }