C# 尽管完成了所有迭代,但带有local的Parallel.Foreach最终还是会停止

C# 尽管完成了所有迭代,但带有local的Parallel.Foreach最终还是会停止,c#,multithreading,task-parallel-library,C#,Multithreading,Task Parallel Library,在我的Parallel.ForEach循环中,所有线程都会调用localFinally委托。 我发现这种情况发生在我的并行循环停止时。 在我的并行循环中,我有大约三个在循环完成之前返回的条件检查阶段。看起来,当线程从这些阶段返回时,而不是整个主体的执行时,它才不会执行localFinally委托 循环结构如下所示: var startingThread = Thread.CurrentThread; Parallel.ForEach(fullList, opt, ()=&g

在我的Parallel.ForEach循环中,所有线程都会调用localFinally委托。 我发现这种情况发生在我的并行循环停止时。 在我的并行循环中,我有大约三个在循环完成之前返回的条件检查阶段。看起来,当线程从这些阶段返回时,而不是整个主体的执行时,它才不会执行localFinally委托

循环结构如下所示:

 var startingThread = Thread.CurrentThread;
 Parallel.ForEach(fullList, opt,
         ()=> new MultipleValues(),
         (item, loopState, index, loop) =>
         {
            if (cond 1)
                return loop;
            if (cond 2)
                {
                process(item);
                return loop;
                }
            if (cond 3)
                return loop;

            Do Work(item);
            return loop;
          },
          partial =>
           {
              Log State of startingThread and threads
            } );
我在一个小数据集上运行了这个循环,并详细记录了日志,发现当Parallel.ForEach完成所有迭代时,localFinally最后一个线程的日志是-- 调用线程状态为线程6循环Indx 16的WaitSleepJoin
循环仍然无法正常完成,并且仍处于暂停状态。。。有什么线索可以解释为什么会有摊位吗


干杯

在看到localFinally的定义(在每个线程完成后执行)后,我刚刚进行了一次快速测试运行,这让我怀疑这可能意味着并行创建的线程将远远少于执行的循环。e、 g

        var test = new List<List<string>> ();
        for (int i = 0; i < 1000; i++)
        {
            test.Add(null);
        }

        int finalcount = 0;
        int itemcount = 0;
        int loopcount = 0;

        Parallel.ForEach(test, () => new List<string>(),
            (item, loopState, index, loop) =>
            {
                Interlocked.Increment(ref loopcount);
                loop.Add("a");
                //Thread.Sleep(100);
                return loop;
            },
            l =>
            {
                Interlocked.Add(ref itemcount, l.Count);                    
                Interlocked.Increment(ref finalcount);                    
            });
var测试=新列表();
对于(int i=0;i<1000;i++)
{
test.Add(null);
}
int finalcount=0;
int itemcount=0;
int loopcount=0;
Parallel.ForEach(test,()=>newlist(),
(项目、循环状态、索引、循环)=>
{
联锁增量(参考循环计数);
循环。添加(“a”);
//睡眠(100);
回路;
},
l=>
{
联锁。添加(参考项目计数,l计数);
联锁增量(参考最终计数);
});
在循环结束时,itemcount和loopcount与预期一样为1000,并且(在我的机器上)finalcount为1或2,具体取决于执行速度。在有以下条件的情况下:当直接返回时,执行速度可能要快得多,并且不需要额外的线程。只有在执行定位工作时,才需要更多的线程。但是,参数(在我的例子中是l)包含所有执行的组合列表。
这可能是日志差异的原因吗?

我想您只是误解了
localFinally
的意思。它不是为每个项目调用的,而是为
Parallel.ForEach()
使用的每个线程调用的。许多项目可以共享同一个线程

它存在的原因是,您可以在每个线程上独立地执行一些聚合,并最终将它们连接在一起。这样,您只需在一小段代码中处理同步(并使其影响您的性能)

例如,如果要计算一组项目的得分总和,可以这样做:

int totalSum = 0;
Parallel.ForEach(
    collection, item => Interlocked.Add(ref totalSum, ComputeScore(item)));
int totalSum = 0;
Parallel.ForEach(
    collection,
    () => 0,
    (item, state, localSum) => localSum + ComputeScore(item),
    localSum => Interlocked.Add(ref totalSum, localSum));
但是在这里,您可以为每个项目调用
Interlocked.Add()
,这可能会很慢。使用
localInit
localFinally
,您可以像这样重写代码:

int totalSum = 0;
Parallel.ForEach(
    collection, item => Interlocked.Add(ref totalSum, ComputeScore(item)));
int totalSum = 0;
Parallel.ForEach(
    collection,
    () => 0,
    (item, state, localSum) => localSum + ComputeScore(item),
    localSum => Interlocked.Add(ref totalSum, localSum));
请注意,代码仅在
localFinally
中使用
Interlocked.Add()
,并在
正文中访问全局状态。这样,同步成本只需支付几次,每使用一个线程一次


注意:我在本例中使用了
联锁
,因为它非常简单,而且非常明显是正确的。如果代码更复杂,我会首先使用
lock
,并尝试仅在需要良好性能时才使用
Interlocked

可能是死锁,其中可能只是伪代码,但在当前状态下将永远无法达到条件3。if(cond2)在条件周围没有括号(因此只有进程(项)在它下面)。@RobertVerpalen no这只是由于省略括号而导致的伪代码中的一个错误…只是一个简单的错误,日志机制是如何实现的?日志机制可能是问题所在吗?有一次,我花了一天的时间寻找一个问题,我的日志有点错误,告诉我这个问题存在(但没有)=p可能你只是误解了
localFinally
的意思。它不是为每个项目调用的,而是为
Paralle.ForEach()
使用的每个线程调用的。而且许多项可以共享同一个线程。您不应该使用
interlocted.Increment
interlocted.Add
方法来避免各种计数器可能出现的线程争用情况吗?在我的本地测试场景中,它们是不稳定的,因此它们应该是线程安全的,并且结果是相同的,但是对于发布的示例,您是对的,应该使用某种类型的锁定used@RobertVerpalen将字段标记为volatile并不能神奇地使其线程安全。特别是,这并不意味着
++
将正常工作,因为
++
不是原子的。嗯,然后再次显示,不稳定是不够的,只是使用线程睡眠快速检查线程数是否增加,我的循环数是987。更改代码以使用联锁功能。谢谢你的头up@svick,所以我注意到,你和克里斯是绝对正确的!从现在起我会记住这一点。为了清楚起见:结果是一样的。非常感谢你的启发性回应。。。在我的实现中,localFinally没有任何用途,只是用于调试目的,因为我的parallel.ForEach循环执行所有迭代,但仍然处于暂停状态。这是localFinally的日志输出,它在调用循环之前打印CurrentThread的状态——调用线程状态是线程6循环Indx 16的WaitSleepJoin,但是,尽管完成了所有的迭代,Parallel.ForEach并没有优雅地退出任何线索,为什么循环不会优雅地终止?非常感谢您的回答!!我设法找到了代码在其中一个调用中阻塞的点,我让循环继续到完成!干杯