C# Parallel.ForEach在3次收益回报后挂起?

C# Parallel.ForEach在3次收益回报后挂起?,c#,parallel.foreach,C#,Parallel.foreach,我遇到了一个问题,其中并行的action.ForEach有时不被调用。我创建了一个简单的玩具程序,在单线程的情况下显示问题: class Program { static void Main(string[] args) { // Any value > 3 here causes Parallel.ForEach to hang on the yield return int workCount = 4; bool inP

我遇到了一个问题,其中并行的action.ForEach有时不被调用。我创建了一个简单的玩具程序,在单线程的情况下显示问题:

class Program
{
    static void Main(string[] args) 
    {
        // Any value > 3 here causes Parallel.ForEach to hang on the yield return
        int workCount = 4;
        bool inProcess = false;

        System.Collections.Generic.IEnumerable<int> getWorkItems()
        {
            while (workCount > 0)
            {
                if (!inProcess)
                {
                    inProcess = true;
                    System.Console.WriteLine($"    Returning work: {workCount}");
                    yield return workCount;
                }
            }
        }

        System.Threading.Tasks.Parallel.ForEach(getWorkItems(),
            new System.Threading.Tasks.ParallelOptions { MaxDegreeOfParallelism = 1 },
            (workItem) =>
            {
                System.Console.WriteLine($"      Parallel start:  {workItem}");
                workCount--;
                System.Console.WriteLine($"      Parallel finish: {workItem}");
                inProcess = false;
            });

        System.Console.WriteLine($"=================== Finished ===================\r\n");
    }
}
。。。它就挂在那里。该操作从不需要1。这是怎么回事

---------------------------编辑:更详细的示例-----------------------

下面是同一个程序,具有更详细的输出,加上一些锁以保护共享值:

static object lockOnMe = new object();
static void Run()
{
    System.Console.WriteLine($"Starting ThreadId: {Thread.CurrentThread.ManagedThreadId}");

    // Any value > 3 here causes Parallel.ForEach to hang on the yield return
    int workCount = 40;
    bool inProcess = false;

    System.Collections.Generic.IEnumerable<int> getWorkItems()
    {
        while (workCount > 0)
        {
            lock(lockOnMe)
            {
                if (!inProcess)
                {
                    inProcess = true;
                    System.Console.WriteLine($"    Returning work: {workCount} ThreadId: {Thread.CurrentThread.ManagedThreadId}");
                    yield return workCount;
                }
            }

            Thread.Sleep(100);
            System.Console.Write($".");
        }
    }

    System.Threading.Tasks.Parallel.ForEach(getWorkItems(),
    new System.Threading.Tasks.ParallelOptions { MaxDegreeOfParallelism = 1 },
    (workItem) =>
    {
        lock(lockOnMe)
        {
            System.Console.WriteLine($"      Parallel start:  {workItem}  ThreadId: {Thread.CurrentThread.ManagedThreadId}");
            Interlocked.Decrement(ref workCount);
            System.Console.WriteLine($"      Parallel finish: {workItem}");
            inProcess = false;

        }
    });

    System.Console.WriteLine($"=================== Finished ===================\r\n");
}

您正处于这样一种状态:在另一个线程的inProcess设置为false之后,返回一个线程的workcount,这将使您处于无限循环中。并行性保护位于从可枚举项返回的项上,而不是隔离生产者和消费者。如果你想让它工作,你需要在任何你在进程或工作计数中得到或设置的地方都加上一把锁

如果您提高了并发级别,甚至工作计数也可能是错误的

编辑

这不起作用的原因是默认选项
Parallel。foreach
用于创建
分区器
以允许缓冲。如果您自己创建
分区器
,并且不允许缓冲,这将按预期工作。基本上,
Partitioner
中有一种启发式方法,可以提前运行并缓存
IEnumerable
的返回,这打破了这里的逻辑

如果希望它按预期工作,请执行以下操作

    private static void Main(string[] args)
    {
        // Any value > 3 here causes Parallel.ForEach to hang on the yield return
        var partitioner = Partitioner.Create(getWorkItems(), EnumerablePartitionerOptions.NoBuffering);

        System.Threading.Tasks.Parallel.ForEach(partitioner,
            new System.Threading.Tasks.ParallelOptions { MaxDegreeOfParallelism = 1  },
            (workItem) =>
            {
                System.Console.WriteLine($"      Parallel start:  {workItem}");
                workCount--;
                System.Console.WriteLine($"      Parallel finish: {workItem}");
                inProcess = false;
            });

        System.Console.WriteLine($"=================== Finished ===================\r\n");
        var s = System.Console.ReadLine();
    }

您正处于这样一种状态:在另一个线程的inProcess设置为false之后,返回一个线程的workcount,这将使您处于无限循环中。并行性保护位于从可枚举项返回的项上,而不是隔离生产者和消费者。如果你想让它工作,你需要在任何你在进程或工作计数中得到或设置的地方都加上一把锁

如果您提高了并发级别,甚至工作计数也可能是错误的

编辑

这不起作用的原因是默认选项
Parallel。foreach
用于创建
分区器
以允许缓冲。如果您自己创建
分区器
,并且不允许缓冲,这将按预期工作。基本上,
Partitioner
中有一种启发式方法,可以提前运行并缓存
IEnumerable
的返回,这打破了这里的逻辑

如果希望它按预期工作,请执行以下操作

    private static void Main(string[] args)
    {
        // Any value > 3 here causes Parallel.ForEach to hang on the yield return
        var partitioner = Partitioner.Create(getWorkItems(), EnumerablePartitionerOptions.NoBuffering);

        System.Threading.Tasks.Parallel.ForEach(partitioner,
            new System.Threading.Tasks.ParallelOptions { MaxDegreeOfParallelism = 1  },
            (workItem) =>
            {
                System.Console.WriteLine($"      Parallel start:  {workItem}");
                workCount--;
                System.Console.WriteLine($"      Parallel finish: {workItem}");
                inProcess = false;
            });

        System.Console.WriteLine($"=================== Finished ===================\r\n");
        var s = System.Console.ReadLine();
    }

这不是一个完整的答案,但以下是我到目前为止学到的:

Parallel.ForEach的行为与传统的多线程方法不同,它不会像示例代码那样难以共享数据。即使在保护共享数据以防止并发访问时,ForEach逻辑上似乎也出现了优化,如果需要联锁并行线程(例如,按顺序处理对象),这种优化将无法正常工作

要了解Parallel.ForEach的奇怪之处,请尝试运行以下代码段:

static void Run()
{
    System.Collections.Generic.IEnumerable<int> getWorkItems()
    {
        int workCount = 9999;
        while (workCount > 0)
        {
            System.Console.Write($"R");
            yield return workCount--;
            Thread.Sleep(10);
        }
    }

    System.Threading.Tasks.Parallel.ForEach(
        getWorkItems(),
        (workItem) => System.Console.Write($"."));
}
。。。等等我对这种行为没有解释,但我猜interator的处理程序正在尝试优化它缓冲输入的量。在任何情况下,此行为都会对尝试在公共对象上同步的代码造成严重破坏


这个故事的寓意是:如果您的处理完全并行并且不需要同步,请使用parallel.Foreach。如果需要同步,请尝试另一种方法,例如将数据预捕获到数组中,或者编写自己的多线程处理程序

不是一个完整的答案,但以下是我到目前为止学到的:

Parallel.ForEach的行为与传统的多线程方法不同,它不会像示例代码那样难以共享数据。即使在保护共享数据以防止并发访问时,ForEach逻辑上似乎也出现了优化,如果需要联锁并行线程(例如,按顺序处理对象),这种优化将无法正常工作

要了解Parallel.ForEach的奇怪之处,请尝试运行以下代码段:

static void Run()
{
    System.Collections.Generic.IEnumerable<int> getWorkItems()
    {
        int workCount = 9999;
        while (workCount > 0)
        {
            System.Console.Write($"R");
            yield return workCount--;
            Thread.Sleep(10);
        }
    }

    System.Threading.Tasks.Parallel.ForEach(
        getWorkItems(),
        (workItem) => System.Console.Write($"."));
}
。。。等等我对这种行为没有解释,但我猜interator的处理程序正在尝试优化它缓冲输入的量。在任何情况下,此行为都会对尝试在公共对象上同步的代码造成严重破坏


这个故事的寓意是:如果您的处理完全并行并且不需要同步,请使用parallel.Foreach。如果需要同步,请尝试另一种方法,例如将数据预捕获到数组中,或者编写自己的多线程处理程序

如果没有正确的同步,不要从多个线程访问共享可变状态。使用
Parallel.ForEach
的全部目的是不在线程之间共享任何状态。@Servy-请查看编辑。我理解为什么我不应该在不受保护的情况下访问可变状态,但这不是竞争条件。即使只有一个线程,并且敏感部分受到锁的保护,这种情况也会发生。考虑到您理解不应该这样做,而且这样做是错误的,因此,试图详细诊断发生这种情况的原因似乎没有什么效果。不管怎样,您都需要重新设计该方法。我知道我没有解释为什么这个程序会输出它所做的事情,因此我没有回答,而是评论。如果没有正确的同步,不要从多个线程访问共享的可变状态。使用
Parallel.ForEach
的全部目的是不在线程之间共享任何状态。@Servy-请查看编辑。我理解为什么我不应该在不受保护的情况下访问可变状态,但这不是竞争条件。即使只有一个线程,并且敏感部分受到锁的保护,这种情况也会发生。考虑到您理解不应该这样做,而且这样做是错误的,因此尝试诊断ex似乎没有什么效果
R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.
RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR
..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..RR..
RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR..
..RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR
....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RR
RR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....RRRR....
RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRRRR......
..RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRRRR....
....RRRRRRRR........RRRRRRRR........R.RRRRRRRR........RRRRRRRR........RRRRRRRR
........RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRR
RR........RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRR
RRRR........R.RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRRRR........
RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRRRR......
..RRRRRRRR........RRRRRRRR........RRRRRRRR........RRRRRRRR........R.RRRRRRRR..
......RRRRRRRRRRRR...