代码之家  ›  专栏  ›  技术社区  ›  Dejan Scott Dorman

了解AsParallel和AsSequential:LINQ查询的哪个部分是并发的?

  •  1
  • Dejan Scott Dorman  · 技术社区  · 9 年前

    我试图了解是否可以使用一个非线程安全类 AsParallel 查询类似于:

    src.Select(item => nonSafeClass.Process(item))
       .AsParallel()
       .Select(item => DoComputationalIntenseButThreadSafeWork(item));
    

    我已尝试运行以下代码,以查看查询链的哪个部分是并行执行的,而不是:

    IEnumerable<int> array = Enumerable.Range(0, short.MaxValue).ToArray();
    array.Select(i =>
        {
            Console.WriteLine("Step One: {0}", Thread.CurrentThread.ManagedThreadId);
            return i;
        }).AsParallel().Select(i =>
            {
                Console.WriteLine("Step Two: {0}", Thread.CurrentThread.ManagedThreadId);
                return i;
    
            }).AsSequential().Select(i =>
                {
                    Console.WriteLine("Step Three: {0}", Thread.CurrentThread.ManagedThreadId);
                    return i;
                }).ToList();
    

    但令我惊讶的是,“第一步”和“第三步”都出现在不同的线程ID上。我希望只在“第二步”中看到不同的线程ID,因为它位于 并行(As Parallel) 和一个 AsSequential 我的想法错了吗?

    1 回复  |  直到 9 年前
        1
  •  1
  •   svick bala    9 年前

    这是因为 deferred execution .

    how chained queries in Linq work .

    如果您将其更改为最简单的情况

    array.Select(i =>
            {
                Console.WriteLine("Step One: {0}", Thread.CurrentThread.ManagedThreadId);
                return i;
            }).Select(i =>
            {
                Console.WriteLine("Step Two: {0}", Thread.CurrentThread.ManagedThreadId);
                return i;
    
            }).Select(i =>
            {
                Console.WriteLine("Step Three: {0}", Thread.CurrentThread.ManagedThreadId);
                return i;
            }).ToList();
    

    您将看到: 步骤1 步骤2 步骤3 步骤1 步骤2 步骤3 ... ...

    现在想象一下,如果你的假设是正确的:

    First Select()在上运行 Thread 1 (主螺纹)。然后你的 AsParallel 在不同的线程上运行,但最后,您的最终 AsSequential() 需要在同一线程上运行,这意味着 并行(As Parallel) 之后在不同的线程上运行 线程1 被阻止。

    你想的流程是: 1 -> x -> 1

    然后 1 -> y -> 1

    等等。

    作为一种优化,当Linq检测到您有一个select后跟 并行(As Parallel) ,它在单独的线程上为每个迭代运行它们。再次,这是因为 1 -> x -> 1 -> y 不会让任何事情“并行”运行。

    尝试运行简化版本:

    array.Select(i =>
            {
                Console.WriteLine("Step One: {0}", Thread.CurrentThread.ManagedThreadId);
                return i;
            }).AsParallel().Select(i =>
            {
                Console.WriteLine("Step Two: {0}", Thread.CurrentThread.ManagedThreadId);
                return i;
    
            }).ToList();
    

    您将看到步骤1和步骤2按“顺序”进行,但每个迭代都在不同的线程上完成。

    但是,AsSequential()将在执行它的主线程上运行。

    因此,我希望第1步和第2步 在不同于调用线程的同一线程上运行 但步骤3在启动链的同一线程上运行。

    如果要实现所描述的行为,只需将查询更改为:

    array.Select(i =>
            {
                Console.WriteLine("Step One: {0}", Thread.CurrentThread.ManagedThreadId);
                return i;
            }).ToList().AsParallel().Select(i =>
            {
                Console.WriteLine("Step Two: {0}", Thread.CurrentThread.ManagedThreadId);
                return i;
    
            }).AsSequential().Select(i =>
            {
                Console.WriteLine("Step Three: {0}", Thread.CurrentThread.ManagedThreadId);
                return i;
            }).ToList();
    

    第一个ToList()求值将在调用线程上运行所有内容,然后AsParallel()在不同的线程上运行每个迭代(取决于ThreadPool的可用性),最后,AsSequential将确保顺序位在调用线程中运行。