C# 为什么Parallel.Foreach会创建无尽的线程?

C# 为什么Parallel.Foreach会创建无尽的线程?,c#,.net,multithreading,task-parallel-library,C#,.net,Multithreading,Task Parallel Library,下面的代码继续创建线程,即使队列为空。。直到最终发生OutOfMemory异常。如果将Parallel.ForEach替换为常规ForEach,则不会发生这种情况。有人知道这可能发生的原因吗 public delegate void DataChangedDelegate(DataItem obj); public class Consumer { public DataChangedDelegate OnCustomerChanged; public DataChangedD

下面的代码继续创建线程,即使队列为空。。直到最终发生OutOfMemory异常。如果将Parallel.ForEach替换为常规ForEach,则不会发生这种情况。有人知道这可能发生的原因吗

public delegate void DataChangedDelegate(DataItem obj);

public class Consumer
{
    public DataChangedDelegate OnCustomerChanged;
    public DataChangedDelegate OnOrdersChanged;

    private CancellationTokenSource cts;
    private CancellationToken ct;
    private BlockingCollection<DataItem> queue;

    public Consumer(BlockingCollection<DataItem> queue) {
        this.queue = queue;
        Start();
    }

    private void Start() {
        cts = new CancellationTokenSource();
        ct = cts.Token;
        Task.Factory.StartNew(() => DoWork(), ct);
    }

    private void DoWork() {

        Parallel.ForEach(queue.GetConsumingPartitioner(), item => {
            if (item.DataType == DataTypes.Customer) {
                OnCustomerChanged(item);
            } else if(item.DataType == DataTypes.Order) {
                OnOrdersChanged(item);
            }
        });
    }
}
public委托无效datachangedelegate(DataItem obj);
公共类消费者
{
公共数据更改客户更改后的Legate;
公共数据更改Legate OnOrdersChanged;
私有取消令牌源cts;
私有取消令牌ct;
私有阻塞收集队列;
公共消费者(阻止收集队列){
this.queue=队列;
Start();
}
私有void Start(){
cts=新的CancellationTokenSource();
ct=cts.Token;
Task.Factory.StartNew(()=>DoWork(),ct);
}
私房{
Parallel.ForEach(queue.GetConsumingPartitioner(),item=>{
if(item.DataType==DataTypes.Customer){
一旦客户变更(项目);
}else if(item.DataType==DataTypes.Order){
订单变更(项目);
}
});
}
}

我认为
Parallel.ForEach()
主要用于处理有界集合。而且它不希望像
GetConsumingPartitioner()
返回的集合那样,其中
MoveNext()
会长时间阻塞

问题在于
Parallel.ForEach()
试图找到最佳的并行度,因此只要
TaskScheduler
允许它运行,它就会启动尽可能多的
Task
s。但是
TaskScheduler
发现有许多
任务需要很长时间才能完成,而且它们什么都没有做(它们会阻塞),因此它会继续启动新的任务

我认为最好的解决方案是设置
MaxDegreeOfParallelism


作为替代方案,您可以使用TPL数据流的
ActionBlock
。这种情况的主要区别在于,
ActionBlock
在没有要处理的项目时不会阻止任何线程,因此线程数量不会接近极限。

我认为
Parallel.ForEach()
主要用于处理有界集合。而且它不希望像
GetConsumingPartitioner()
返回的集合那样,其中
MoveNext()
会长时间阻塞

问题在于
Parallel.ForEach()
试图找到最佳的并行度,因此只要
TaskScheduler
允许它运行,它就会启动尽可能多的
Task
s。但是
TaskScheduler
发现有许多
任务需要很长时间才能完成,而且它们什么都没有做(它们会阻塞),因此它会继续启动新的任务

我认为最好的解决方案是设置
MaxDegreeOfParallelism


作为替代方案,您可以使用TPL数据流的
ActionBlock
。这种情况的主要区别在于,
ActionBlock
在没有要处理的项目时不会阻止任何线程,因此线程数量不会接近限制。

在任务并行库内部,Parallel.For和Parallel.Foreach遵循爬山算法来确定操作应使用多少并行性

或多或少,他们从一个任务开始运行身体,然后移动到两个任务,依此类推,直到达到一个断点,他们需要减少任务数量

这对于快速完成的方法体非常有效,但是如果方法体需要很长时间才能运行,则可能需要很长时间才能意识到它需要减少并行量。在此之前,它会继续添加任务,并可能导致计算机崩溃

我在任务并行库的一位开发人员的一次演讲中学习了上述内容


指定MaxDegreeOfParallelism可能是最简单的方法。

在任务并行库内部,Parallel.For和Parallel.Foreach遵循爬山算法确定操作应使用多少并行性

或多或少,他们从一个任务开始运行身体,然后移动到两个任务,依此类推,直到达到一个断点,他们需要减少任务数量

这对于快速完成的方法体非常有效,但是如果方法体需要很长时间才能运行,则可能需要很长时间才能意识到它需要减少并行量。在此之前,它会继续添加任务,并可能导致计算机崩溃

我在任务并行库的一位开发人员的一次演讲中学习了上述内容


指定MaxDegreeOfParallelism可能是最简单的方法。

生产者/消费者模式主要用于只有一个生产者和一个消费者的情况

但是,您试图实现的(多个使用者)更符合工作列表模式。下面的代码是从犹他大学的一个并行编程类的UNIT2幻灯片“2C共享内存模式”中获取的,该代码可以在

下载。
BlockingCollection工作列表;
取消源cts;
整数项计数
公开募捐
{
int num_工人=4;
//创建工作列表,填充初始工作
工作列表=新建BlockingCollection(
新的ConcurrentQueue(GetInitialWork());
cts=新的CancellationTokenSource();
itemcount=worklist.Count();
对于(int i=0;i0);
}最后{
如果(!cts.IsCancellationRequested)
BlockingCollection<Item> workList;
CancellationTokenSource cts;
int itemcount

public void Run()
{
  int num_workers = 4;

  //create worklist, filled with initial work
  worklist = new BlockingCollection<Item>(
    new ConcurrentQueue<Item>(GetInitialWork()));

  cts = new CancellationTokenSource();
  itemcount = worklist.Count();

  for( int i = 0; i < num_workers; i++)
    Task.Factory.StartNew( RunWorker );
}

IEnumberable<Item> GetInitialWork() { ... }

public void RunWorker() {
  try  {
    do {
      Item i = worklist.Take( cts.Token );
      //blocks until item available or cancelled
          Process(i);
      //exit loop if no more items left
    } while (Interlocked.Decrement( ref itemcount) > 0);
  } finally {
      if( ! cts.IsCancellationRequested )
        cts.Cancel();
    }
  }
}

public void AddWork( Item item) {
  Interlocked.Increment( ref itemcount );
  worklist.Add(item);
}

public void Process( Item i ) 
{
  //Do what you want to the work item here.
}