我试图理解我是否可以使用非线程安全类留给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,因为它位于AsParallel
和AsSequential
之间。我的想法错了吗?
答案 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将确保顺序位运行在调用线程。