C# Parallel.ForEach中的迭代器在多个线程上运行

C# Parallel.ForEach中的迭代器在多个线程上运行,c#,.net,multithreading,task-parallel-library,parallel.foreach,C#,.net,Multithreading,Task Parallel Library,Parallel.foreach,我有一些代码在Parallel.ForEach循环中运行。循环中的代码是线程安全的,但我使用的迭代器(一个带有yield return的自定义方法)不是线程安全的。迭代器似乎正在多个线程上运行,这可能会导致问题 问题背景 迭代器包含对NHibernate的调用(尽管这完全是偶然的,您将在后面看到),由于我在并行代码时遇到问题,我在NHibernate Profiler中查看了一下,看看这是否能对情况有所帮助。部分时间,它开始报告“在多个线程中使用单个会话很可能是一个bug” 现在,根据NHibe

我有一些代码在
Parallel.ForEach
循环中运行。循环中的代码是线程安全的,但我使用的迭代器(一个带有
yield return
的自定义方法)不是线程安全的。迭代器似乎正在多个线程上运行,这可能会导致问题

问题背景

迭代器包含对NHibernate的调用(尽管这完全是偶然的,您将在后面看到),由于我在并行代码时遇到问题,我在NHibernate Profiler中查看了一下,看看这是否能对情况有所帮助。部分时间,它开始报告“在多个线程中使用单个会话很可能是一个bug”

现在,根据NHibernate探查器,有问题的代码在迭代器中(因此它没有试图在
Parallel.ForEach
操作的其他地方实现某些内容)。所以我添加了一些我自己的代码,这样我就可以检测出NHibernate分析器是什么,我看到了同样的东西

迭代器方法是从多个线程调用的-我认为这是不可能的,因为其他人似乎也这么认为,例如,另一个答案:

问题的简化演示

为了演示这个问题(不需要我处理NHibernate等的所有无关gubbins),我编写了一个简单的控制台应用程序,显示了相同的问题:

public class Program
{
    public static void Main(string[] args)
    {
        Parallel.ForEach(YieldedNumbers(), (n) => { Thread.Sleep(n); });
        Console.WriteLine("Done!");
        Console.ReadLine();
    }

    public static IEnumerable<int> YieldedNumbers()
    {
        Random rnd = new Random();
        int lastKnownThread = Thread.CurrentThread.ManagedThreadId;
        int detectedSwitches = 0;
        for (int i = 0; i < 1000; i++)
        {
            int currentThread = Thread.CurrentThread.ManagedThreadId;
            if (lastKnownThread != currentThread)
            {
                detectedSwitches++;
                Console.WriteLine(
                    $"{detectedSwitches}: Last known thread ({lastKnownThread}) is not the same as the current thread ({currentThread}).");
                lastKnownThread = currentThread;
            }
            yield return rnd.Next(100,250);
        }
    }
}
公共类程序
{
公共静态void Main(字符串[]args)
{
Parallel.ForEach(YieldedNumbers(),(n)=>{Thread.Sleep(n);});
控制台。WriteLine(“完成!”);
Console.ReadLine();
}
公共静态IEnumerable yieldNumber()
{
随机rnd=新随机();
int lastKnownThread=Thread.CurrentThread.ManagedThreadId;
int-detectedSwitches=0;
对于(int i=0;i<1000;i++)
{
int currentThread=Thread.currentThread.ManagedThreadId;
如果(lastKnownThread!=currentThread)
{
检测开关++;
控制台写入线(
$“{DetectedSwitchs}:最后一个已知线程({lastKnownThread})与当前线程({currentThread})不同。”;
lastKnownThread=currentThread;
}
收益回报率rnd.Next(100250);
}
}
}
在我的测试运行中,线程在1000次迭代中切换157到174次。
Sleep
模拟我的操作所花费的时间

摘要

如果.NET中实现的迭代器模式本质上不是线程安全的,为什么
Parallel.ForEach
会这样做?及;如果能够以安全的方式(在一个线程上)获取迭代器当前公开的数据,并且在多个线程上处理它,那么什么是一个好的解决方案?(例如,是否有任何方法可以强制迭代器返回到一个线程上?或者迭代器是否也必须是线程安全的,以及每次迭代调用的操作?或者完全是其他解决方案?)

版本历史

  • 更新了总结,希望避免或减少我原来问题的XY问题
有没有办法强制迭代器回到一个线程上

不,您必须显式处理多个线程调用迭代器的情况。在幕后,如果多个线程正在调用
IEnumerator.MoveNext()
,迭代器将继续前进。这里没有发生隐式同步


关于使用锁定进行迭代。尽管我必须说这看起来像是XY问题,但您是否应该从多个线程并行调用
NHibernate
?它是上下文线程安全的吗?这些问题是在使用线程安全迭代器进入野外之前应该考虑的问题。

据我所知,因为在.NET中通过<代码> IEnumerator < /COD>接口实现的迭代器模式本质上不是线程安全的(在乔恩Sket的blog中,您引用的示例显示了将出现的竞争条件)。那么我希望NHibernate只从一个线程调用。但是,如果我将迭代器与
Parallel.ForEach
一起使用,结果就不是这样了。所以,也许更好的问题是:为什么会发生这种情况?及;安全地(从一个线程)获取数据,但在多个线程中处理数据,什么是一个好的解决方案?@Colin那么我认为NHibernate只能从一个线程调用,在这种情况下正好相反。很简单,迭代器是一个实例,由多个线程迭代。没有一个线程像“生产者-消费者”那样负责迭代。它更像是狂野的西部。