我有一些我在Parallel.ForEach
循环中运行的代码。循环中的代码是线程安全的,但我使用的迭代器(带有yield return
的自定义方法)不是。似乎迭代器正在多个线程上运行,这可能会导致问题。
问题的背景
迭代器包含对NHibernate的调用(虽然你后面会看到它完全是偶然的)并且因为我在NHibernate Profiler中查看代码的并行化问题,看看如果它可以发光一些关于这种情况。部分通过它开始报告"在多个线程中使用单个会话可能是一个错误"。
现在,根据NHibernate Profiler的说法,有问题的代码在迭代器中(所以它并没有试图在Parallel.ForEach
动作中实现其他地方)。所以我添加了一些自己的代码,这样我就可以检测到NHibernate Profiler是什么了,我看到了同样的事情。
迭代器方法是从多个线程调用的 - 我认为这是不可能的,正如其他人似乎也想到的那样,例如另一个SO回答:https://stackoverflow.com/a/26553810/8152
问题的简化演示
为了演示这个问题(不需要我所有无关的gubbins处理NHibernate等),我写了一个简单的控制台应用程序来显示同样的问题:
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);
}
}
}
在我的测试运行中,线程在1000次迭代中切换157到174次。 Sleep
模拟我的行动所需的时间。
摘要
如果在.NET中实现的迭代器模式本身不是线程安全的,为什么Parallel.ForEach
会这样做呢?和;什么是一个很好的解决方案,能够以安全的方式(在一个线程上)获取迭代器当前公开的数据,然后在多个线程上处理它? (例如,有没有办法强制迭代器返回到一个线程?或者迭代器也必须是线程安全的,以及为每次迭代调用的操作?还是完全是其他解决方案?)
版本历史记录
答案 0 :(得分:0)
有没有办法强制迭代器重新组合到一个线程上?
不,您必须显式处理多个线程调用迭代器的情况。在幕后,如果多个线程正在调用IEnumerator.MoveNext()
,迭代器将继续按照它所告知的那样前进。这里没有发生隐式同步。
@JonSkeet blogged a while back关于使用锁定进行迭代。虽然我必须说这看起来像XY问题,你应该从多个线程并行调用NHibernate
吗?它的上下文线程是否安全?这些是在使用线程安全迭代器进入野外之前应该考虑的问题。