我有一个简单的程序,它迭代了作为反馈枚举器实现的无数可枚举。我已在TPL和PLINQ中实现了这一点。两个示例在可预测的迭代次数后锁定:PLINQ为8,TPL为3。代码是在不使用TPL / PLINQ的情况下执行的,运行正常。我已经以非线程安全的方式实现了枚举器以及线程安全的方式。如果并行度限制为1,则可以使用前者(如示例中的情况)。非线程安全的枚举器非常简单,不依赖于任何“花哨的”.NET库类。如果我增加并行度,则在死锁之前执行的迭代次数增加,例如对于PLINQ,迭代次数是8 *并行度。
以下是迭代器:
枚举器(非线程安全)
public class SimpleEnumerable<T>: IEnumerable<T>
{
private T _value;
private readonly AutoResetEvent _releaseValueEvent = new AutoResetEvent(false);
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<T> GetEnumerator()
{
while(true)
{
_releaseValueEvent.WaitOne();
yield return _value;
}
}
public void OnNext(T value)
{
_value = value;
_releaseValueEvent.Set();
}
}
枚举器(线程安全)
public class SimpleEnumerable<T>: IEnumerable<T>
{
private readonly BlockingCollection<T> _blockingCollection = new BlockingCollection<T>();
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<T> GetEnumerator()
{
while(true)
{
yield return _blockingCollection.Take();
}
}
public void OnNext(T value)
{
_blockingCollection.Add(value);
}
}
PLINQ示例:
public static void Main(string[] args)
{
var enumerable = new SimpleEnumerable<int>();
enumerable.OnNext(0);
enumerable
.Do(i => Debug.WriteLine($"{i} {Thread.CurrentThread.ManagedThreadId}"))
.AsParallel()
.WithDegreeOfParallelism(1)
.ForEach
(
i =>
{
Debug.WriteLine($"{i} {Thread.CurrentThread.ManagedThreadId}");
enumerable.OnNext(i+1);
}
);
}
TPL示例:
public static void Main(string[] args)
{
var enumerable = new SimpleEnumerable<int>();
enumerable.OnNext(0);
Parallel.ForEach
(
enumerable,
new ParallelOptions { MaxDegreeOfParallelism = 1},
i =>
{
Debug.WriteLine($"{i} {Thread.CurrentThread.ManagedThreadId}");
enumerable.OnNext(i+1);
}
);
}
根据我对callstack的分析,似乎在PLINQ和TPL中的分区相关方法中都存在死锁,但我不确定如何解释它。
通过反复试验,我发现enumerable
中的PLINQ Partitioner.Create(enumerable, EnumerablePartitionerOptions.NoBuffering)
包裹修复了问题,但我不确定为什么会发生死锁。
我非常有兴趣找出错误的根本原因。
请注意,这是一个人为的例子。我不是在寻找对代码的批评,而是为什么是发生的死锁。具体来说,在PLINQ示例中,如果注释掉.AsParallel()
和.WithDegreeOfParallelism(1)
行,则代码可以正常工作。
答案 0 :(得分:2)
您实际上并没有逻辑的值序列,因此首先尝试创建IEnumerable
根本没有任何意义。此外,您几乎肯定不会尝试创建可由多个线程使用的IEnumerator
。存在着疯狂,仅仅是因为IEnumerator
暴露的界面并没有真正暴露出你想要的东西才能做到这一点。您可以创建一个IEnumerator
,它只会由单个线程使用,该线程根据多个线程使用的基础数据源计算要返回的数据,因为它非常不同。 / p>
如果您只是想创建一个在不同线程中运行的生产者和消费者,请不要创建自己的&#34;包装器&#34;在BlockingCollection
附近,*只需使用BlockingCollection
。让生产者添加它,消费者从中读取。消费者可以使用GetConsumingEnumerable
,如果它只想在获取这些项目时迭代项目(想要做的常见操作)。