Как сделать так, чтобы несколько потоков обрабатывали один и тот же IEnumerable результат? - PullRequest
8 голосов
/ 23 февраля 2011

У меня есть метод, который возвращает IEnumerable<string>, который, конечно, обрабатывается с помощью yield return <string>;. Я хочу иметь несколько потоков, обрабатывающих результат этого, конечно, без повторения его и быть потокобезопасным. Как бы я этого достиг?

var result = GetFiles(source);

for (int i = 0; i < Environment.ProcessorCount; i++)
{
    tasks.Add(Task.Factory.StartNew(() => { ProcessCopy(result); }));
}

Task.WaitAll(tasks.ToArray());

Однако, похоже, это приводит к повторениям:

C:\Users\esac\Pictures\2000-06\DSC_1834.JPG
C:\Users\esac\Pictures\2000-06\DSC_1835.JPG
C:\Users\esac\Pictures\2000-06\.picasa.ini
C:\Users\esac\Pictures\2000-06\DSC_1834.JPG
C:\Users\esac\Pictures\2000-06\DSC_1835.JPG
C:\Users\esac\Pictures\2000-06\.picasa.ini
C:\Users\esac\Pictures\2000-06\DSC_1834.JPG
C:\Users\esac\Pictures\2000-06\DSC_1835.JPG
C:\Users\esac\Pictures\2000-06\.picasa.ini
C:\Users\esac\Pictures\2000-06\DSC_1834.JPG
C:\Users\esac\Pictures\2000-06\DSC_1835.JPG

Ответы [ 3 ]

9 голосов
/ 23 февраля 2011

Вы можете легко сделать это, используя метод Parallel.ForEach.

Написать простой цикл Parallel.ForEach

Каждая итерация будет поставлена ​​в очередь в диспетчере задач.Цикл завершится, когда будут выполнены все итерации.

var result = GetFiles(source);

Parallel.ForEach(result, current => {
    ProcessCopy(current);
});

Console.WriteLine("Done");
4 голосов
/ 23 февраля 2011

Вы должны выбрать диапазон элементов для каждого вызова ProcessCopy() - сейчас вы передаете каждому потоку полное перечисление файлов - помните, что передаваемый вами IEnumerable имеет метод с именем GetEnumerator() - только когда этот метод вызывается (что foreach делает для вас скрытно), возвращается реальный перечислитель, с помощью которого вы можете затем перечислять элементы по одному. Поскольку вы передаете IEnumerable, каждый поток вызывает GetEnumerator() и, следовательно, перечисляет все файлы.

Вместо этого сделайте что-то подобное, чтобы каждый ProcessCopy() обрабатывал один файл:

foreach(string file in GetFiles(source))
{
    string fileToProcess = file;
    tasks.Add(Task.Factory.StartNew(() => { ProcessCopy(fileToProcess); }));
}

Task.WaitAll(tasks.ToArray());

Я бы не стал беспокоиться о количестве процессоров - пусть TPL и пул потоков выяснят, сколько потоков нужно запустить для достижения оптимальной производительности.

1 голос
/ 23 февраля 2011

Почему бы не использовать простой запрос LINQ, чтобы сделать то, что вы хотите?

var tasks =
    from f in GetFiles(source)
    select Task.Factory.StartNew(() => { ProcessCopy(f); });

Task.WaitAll(tasks.ToArray());

За кулисами TPL все равно обрабатывает все непристойные вещи Environment.ProcessorCount.

...