理解 AsParallel 和 AsSequential:LINQ 查询的哪一部分是并发的? [英] Understanding AsParallel and AsSequential: which part of the LINQ query is concurrent?
问题描述
我试图了解是否可以使用留给 AsParallel
查询的非线程安全类.类似的东西:
src.Select(item => nonSafeClass.Process(item)).AsParallel().Select(item => DoComputationalIntenseButThreadSafeWork(item));
我尝试运行以下代码以查看查询链的哪一部分并行执行,哪些不是:
IEnumerable数组 = Enumerable.Range(0, short.MaxValue).ToArray();array.Select(i =>{Console.WriteLine("第一步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).AsParallel().Select(i =>{Console.WriteLine("第二步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).AsSequential().Select(i =>{Console.WriteLine("第三步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).ToList();
但令我惊讶的是,第一步"和第三步"出现在不同的线程 ID 上.我期待只为第二步"看到不同的线程 ID,因为它介于 AsParallel
和 AsSequential
之间.我的想法错了吗?
这是因为 延迟执行.
如果换成最简单的情况
array.Select(i =>{Console.WriteLine("第一步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).Select(i =>{Console.WriteLine("第二步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).Select(i =>{Console.WriteLine("第三步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).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("第一步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).AsParallel().Select(i =>{Console.WriteLine("第二步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).ToList();
您将看到第 1 步和第 2 步按顺序"进行,但每次迭代都在不同的线程上完成.
然而,您的 AsSequential() 将在执行它的主线程上运行.
因此,我希望第 1 步和第 2 步在与调用线程不同的同一线程上运行,但第 3 步在启动链的同一线程上运行.>
如果您想实现您所描述的行为,只需将您的查询更改为:
array.Select(i =>{Console.WriteLine("第一步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).ToList().AsParallel().Select(i =>{Console.WriteLine("第二步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).AsSequential().Select(i =>{Console.WriteLine("第三步:{0}", Thread.CurrentThread.ManagedThreadId);返回我;}).ToList();
第一个 ToList() 评估将在调用线程上运行所有内容,然后 AsParallel() 在不同线程上运行每次迭代(取决于 ThreadPool 可用性),最后,您的 AsSequential 将确保顺序位在调用线程.
I'm trying to understand if I can use a non thread-safe class left to a AsParallel
query. Something like:
src.Select(item => nonSafeClass.Process(item))
.AsParallel()
.Select(item => DoComputationalIntenseButThreadSafeWork(item));
I've tried to run the following code to see which part of the query chain is executed in parallel in which is not:
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();
But to my surprise, both "Step One" and "Step Three" appear on different thread IDs. I was expecting to see different thread IDs only for "Step Two" because it is between a AsParallel
and a AsSequential
. Is my thinking wrong?
This is because of deferred execution.
And how chained queries in Linq work.
If you change it to the simplest case of
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();
You will see this: Step 1 Step 2 Step 3 Step 1 Step 2 Step 3 ... ...
Now imagine if your assumption was correct:
First Select() runs on Thread 1
(main thread). Then your AsParallel
runs on a different thread but finally, your final AsSequential()
needs to run on the same thread, meaning it doesn't make any difference to have the AsParallel
running on a different thread since Thread 1
is blocked.
The flow you were thinking would be:
1 -> x -> 1
followed by
1 -> y -> 1
and so on and so forth.
As an optimization, when Linq detects that you have a select followed by AsParallel
, it runs them for each iteration on a separate thread. Again, this is because going from 1 -> x -> 1 -> y
wouldn't make anything run in "parallel".
Try it by running a simplified version:
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();
You will see that Step 1 and Step 2 are don in "sequence" but each iteration is done on a different thread.
Your AsSequential() however will run on the main thread that executed it.
As such, I would expect Step 1 and Step 2 to run on the same thread which is different from the calling thread but Step 3 to run on the same thread that started the chain.
If you want to achieve the behavior you described, simply change your query to this:
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();
The first ToList() evaluation will run everything on the calling thread, the AsParallel() then runs each iteration on a different thread (subject to ThreadPool availability) and finally, your AsSequential will make sure the sequential bits are run on the calling thread.
这篇关于理解 AsParallel 和 AsSequential:LINQ 查询的哪一部分是并发的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!