2

У меня есть код, который я запускаю в цикле Parallel.ForEach. Код в цикле является потокобезопасным, но итератор, который я использую (настраиваемый метод с yield return), нет. Похоже, что итератор запускается на нескольких потоках, что может вызвать проблемы.Итератор в Parallel.ForEach запускается на нескольких потоках

Предыстория проблемы

итератора содержит вызовы NHibernate (Althought это совершенно случайно, как вы увидите позже), и, как я имел проблемы parallelising код я посмотрел в NHibernate Profiler, чтобы увидеть, что если которые могли бы осветить ситуацию. Частично через него началось сообщение «Использование одного сеанса в нескольких потоках, вероятно, является ошибкой».

Теперь, согласно NHibernate Profiler, код нарушителя находился внутри итератора (поэтому он не пытался материализовать что-либо в другом месте в действии Parallel.ForEach). Поэтому я добавил некоторые из своих собственных кодов, чтобы я мог определить, что такое NHibernate Profiler, и я видел то же самое.

Метод итератора вызывается из нескольких потоков, что, как я думал, было невозможным, как думают другие, например. это другой SO ответа: https://stackoverflow.com/a/26553810/8152

Упрощенной демонстрация проблемы

Чтобы продемонстрировать проблему (без необходимости всего моего лишнего Габбинса дела с 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); 
     } 
    } 
} 

Из моих тестовых прогонов поток переключается между 157 и 174 раз в 1000 итераций. Sleep имитирует время моего действия.

Резюме

Почему Parallel.ForEach это сделать, если шаблон итератора как это реализовано в .NET изначально не поточно-? а также; Что было бы хорошим решением для получения данных, которые в настоящее время предоставляет итератор, безопасным способом (в одном потоке), но обрабатывает его на нескольких потоках? (например, есть ли способ принудительно заменить итератор на один поток? Или должен ли итератор быть потокобезопасным, а также действие, которое вызывается для каждой итерации? Или какое-либо другое решение полностью?)

История версий

  • Обновлено резюме, чтобы надеяться избежать или уменьшить проблему XY моего первоначального вопроса.

ответ

0

Есть ли способ принудительно заменить итератор на одну резьбу?

Нет, вы должны явно обработать условие, когда несколько потоков вызывают итератор. За кулисами, если несколько потоков вызывают IEnumerator.MoveNext(), итератор будет продолжать продвигаться столько, сколько ему говорят. Здесь нет никакой неявной синхронизации.

@JonSkeet blogged a while back об итерировании с блокировкой.Хотя я должен сказать, что это похоже на проблему XY для меня, следует ли вы звонить NHibernate параллельно из нескольких потоков? Является ли это контекстно-зависимым потоком? Это вопросы, которые вы должны рассмотреть перед тем, как отправиться в дикую природу с помощью итераторов с поддержкой потоков.

+0

Насколько я знаю, поскольку шаблон итератора, реализованный в .NET через интерфейс IEnumerator, по сути не является потокобезопасным (пример в блоге Jon Skeet, на котором вы ссылаетесь, показывает состояние гонки, которое произойдет), тогда я ожидал бы, что NHibernate только вызывается из одного потока. Однако, оказывается, это не так, если я использую итератор с 'Parallel.ForEach'. Итак, может быть, вопросы лучше: почему это происходит? а также; Что было бы хорошим решением для безопасного хранения данных (из одного потока), но обработать его в нескольких потоках? –

+0

@Colin *, то я бы ожидал, что NHibernate только вызывается из одного потока * В этом случае верно противоположное. Это просто, итератор - это единственный экземпляр, который повторяется несколькими потоками. Нет ни одной нити, отвечающей за итерацию «производителя-потребителя», как мода. Это больше похоже на дикий запад. –

Смежные вопросы