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

时间:2015-03-18 13:06:02

标签: c# task-parallel-library plinq

我试图理解我是否可以使用非线程安全类留给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,因为它位于AsParallelAsSequential之间。我的想法错了吗?

1 个答案:

答案 0 :(得分:1)

这是因为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()需要在同一个线程上运行,这意味着让AsParallel在不同的线程上运行没有任何区别自Thread 1被阻止后的线程。

您正在考虑的流程是: 1 -> x -> 1

接下来是 1 -> y -> 1

依此类推。

作为优化,当Linq检测到您有一个select后跟AsParallel时,它会在单独的线程上为每次迭代运行它们。同样,这是因为从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将确保顺序位运行在调用线程。