Parallel.ForEach中的迭代器在多个线程

时间:2016-08-31 15:35:25

标签: c# .net multithreading task-parallel-library parallel.foreach

我有一些我在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会这样做呢?和;什么是一个很好的解决方案,能够以安全的方式(在一个线程上)获取迭代器当前公开的数据,然后在多个线程上处理它? (例如,有没有办法强制迭代器返回到一个线程?或者迭代器也必须是线程安全的,以及为每次迭代调用的操作?还是完全是其他解决方案?)

版本历史记录

  • 更新了摘要,希望避免或减少原始问题的XY问题。

1 个答案:

答案 0 :(得分:0)

  

有没有办法强制迭代器重新组合到一个线程上?

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

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