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

C# 理解AsParallel和AsSequential:LINQ查询的哪一部分是并发的?,c#,task-parallel-library,plinq,C#,Task Parallel Library,Plinq,我试图理解我是否可以使用一个非线程安全的类来进行AsParallel查询。比如: src.Select(item => nonSafeClass.Process(item)) .AsParallel() .Select(item => DoComputationalIntenseButThreadSafeWork(item)); 我尝试运行以下代码,以查看查询链的哪个部分是并行执行的,哪个部分不是: IEnumerable<int> array = Enum

我试图理解我是否可以使用一个非线程安全的类来进行
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();
IEnumerable数组=可枚举的.Range(0,short.MaxValue).ToArray();
array.Select(i=>
{
WriteLine(“第一步:{0}”,Thread.CurrentThread.ManagedThreadId);
返回i;
}).AsParallel().Select(i=>
{
WriteLine(“第二步:{0}”,Thread.CurrentThread.ManagedThreadId);
返回i;
}).AsSequential().Select(i=>
{
WriteLine(“第三步:{0}”,Thread.CurrentThread.ManagedThreadId);
返回i;
}).ToList();
但令我惊讶的是,“第一步”和“第三步”都出现在不同的线程ID上。我希望只在“第二步”中看到不同的线程ID,因为它介于
AsParallel
AsSequential
之间。我的想法错了吗?

这是因为

如果你把它改成最简单的

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();
您将看到: 第一步 步骤2 步骤3 第一步 步骤2 步骤3 ...

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

First Select()在
线程1
(主线程)上运行。然后,您的
AsParallel
在不同的线程上运行,但最后,您的final
AsSequential()
需要在同一个线程上运行,这意味着
AsParallel
在不同的线程上运行没有任何区别,因为
thread 1
被阻塞

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

1->y->1

等等等等

作为一种优化,当Linq检测到您有一个select,后跟
aspallel
,它会在一个单独的线程上为每个迭代运行它们。同样,这是因为从
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()在不同的线程上运行每个迭代(取决于线程池的可用性),最后,AsSequential将确保顺序位在调用线程上运行。

只要读取是线程安全的,并且您只执行读取,你该走了。不过,写操作是另一个问题……您必须了解的是IEnumerables是懒惰的—它们只在需要时使用—因此,如果您在某个地方有一个AsParalle,则
非安全类。稍后将在可能的不同线程上调用进程。如果你不想要这个,就用
src.Select(…).ToArray()…
@CarstenKönig强迫它,例如:我怀疑这样的事情。尽管如此,我还是不明白assuential的用法。另外,在我的实际代码中,在AsParallel左侧的“非线程安全部分”中,我有一个DbDataReader,我在一个
中前进以产生返回。而且它有效!只是我不知道为什么。我怀疑DbDataReader是线程安全的…@CarstenKönig:啊,我在这里找到了这个问题的答案:你说的话确实有道理。谢谢。只是在我的例子中,使用
ToList
强制求值不是一个选项,因为我有很多元素要处理(不适合内存)。更奇怪的是,我确实迭代了一个DbDataReader,我怀疑它是线程安全的。但如果需要,我将在单独的问题中对此进行检查。啊,我在这里找到了DbDataReader案例的答案: