如何限制AsParallel()预先读取并放入其内部缓冲区的项目数量?
以下是一个例子:
int returnedCounter;
IEnumerable<int> Enum()
{
while (true)
yield return Interlocked.Increment(ref returnedCounter);
}
[TestMethod]
public void TestMethod1()
{
foreach (var i in Enum().AsParallel().Select(a => a))
{
Thread.Sleep(3000);
break;
}
Console.WriteLine(returnedCounter);
}
我消耗1项,睡觉,停止枚举。它在我的机器上打印526400。在我的真实项目中,每个项目分配数千KB。 AsParallel()预先读取了很多项目,导致非常糟糕的内存消耗和CPU浪费。
使用WithMergeOptions(ParallelMergeOptions.NotBuffered)有点帮助。它打印4544.但它对我来说仍然太多了。
在Enum()中等待冻结主线程中的循环。
答案 0 :(得分:4)
关于Partitioners的另一个问题!
在您的情况下,您必须找到/写一个一次只需要一个项目的分区程序。
的文章<强>更新强>
我只记得我看到SingleItemPartitioner
实施的位置:它位于ParallelExtensionsExtras
项目中Samples for Parallel Programming with the .NET Framework
我也刚读过你的测试代码。我可能应该第一次这样做!
此代码:
Enum().AsParallel().Select(a => a)
表示:接受Enum()
并尽可能快地枚举它,并返回一个新的IEnumerable<int>
。
因此,foreach
不会从Enum()
中提取项目 - 它会从linq语句创建的新IEnumerable<int>
中提取项目。
此外,您的foreach
在主线程上运行,因此每个项目的工作都是单线程的。
如果您想并行运行,但只在需要时生成一个项目,请尝试:
Parallel.ForEach( SingleItemPartitioner.Create( Enum() ), ( i, state ) =>
{
Thread.Sleep( 3000 );
state.Break();
}
答案 1 :(得分:0)
找到了解决方法。
首先,让我澄清一下原来的问题。我需要一个可以在无限序列上运行的可管理流水线。管道是:
Enum()
AsParallel().Select(a => a)
foreach
body 步骤3可能会暂停管道。这是由Sleep()
模仿的。问题是当pipleine暂停时,第2步会提取太多元素。
PLinq必须有一些内部队列。无法显式配置队列大小。但是大小取决于ParallelMergeOptions
。 ParallelMergeOptions.NotBuffered
会降低队列大小,但对我来说大小仍然太大。
我的解决方法是知道正在处理的项目数量,达到限制时停止并行处理,再次启动管道时重新启动并行处理。
int sourceCounter;
IEnumerable<int> SourceEnum() // infinite input sequence
{
while (true)
yield return Interlocked.Increment(ref sourceCounter);
}
[TestMethod]
public void PlainPLinq_PausedConsumtionTest()
{
sourceCounter = 0;
foreach (var i in SourceEnum().AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered).Select(a => a))
{
Thread.Sleep(3000);
break;
}
Console.WriteLine("fetched from source sequence: {0}", sourceCounter); // prints 4544 on my machine
}
[TestMethod]
public void MyParallelSelect_NormalConsumtionTest()
{
sourceCounter = 0;
foreach (var i in MyParallelSelect(SourceEnum(), 64, a => a))
{
if (sourceCounter > 1000000)
break;
}
Console.WriteLine("fetched from source sequence: {0}", sourceCounter);
}
[TestMethod]
public void MyParallelSelect_PausedConsumtionTest()
{
sourceCounter = 0;
foreach (var i in MyParallelSelect(SourceEnum(), 64, a => a))
{
Thread.Sleep(3000);
break;
}
Console.WriteLine("fetched from source sequence: {0}", sourceCounter);
}
class DataHolder<D> // reference type to store class or struct D
{
public D Data;
}
static IEnumerable<DataHolder<T>> FetchSourceItems<T>(IEnumerator<T> sourceEnumerator, DataHolder<int> itemsBeingProcessed, int queueSize)
{
for (; ; )
{
var holder = new DataHolder<T>();
if (Interlocked.Increment(ref itemsBeingProcessed.Data) > queueSize)
{
// many enought items are already being processed - stop feeding parallel processing
Interlocked.Decrement(ref itemsBeingProcessed.Data);
yield break;
}
if (sourceEnumerator.MoveNext())
{
holder.Data = sourceEnumerator.Current;
yield return holder;
}
else
{
yield return null; // return null DataHolder to indicate EOF
yield break;
}
}
}
IEnumerable<OutT> MyParallelSelect<T, OutT>(IEnumerable<T> source, int queueSize, Func<T, OutT> selector)
{
var itemsBeingProcessed = new DataHolder<int>();
using (var sourceEnumerator = source.GetEnumerator())
{
for (;;) // restart parallel processing
{
foreach (var outData in FetchSourceItems(sourceEnumerator, itemsBeingProcessed, queueSize).AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered).Select(
inData => inData != null ? new DataHolder<OutT> { Data = selector(inData.Data) } : null))
{
Interlocked.Decrement(ref itemsBeingProcessed.Data);
if (outData == null)
yield break; // EOF reached
yield return outData.Data;
}
}
}
}