Получаете странный результат при использовании Task Parallel Library?
Я пытаюсь сделать некоторые задачи фильтра с использованием TPL. Здесь я упрощаю код для фильтрации числа на основе условия. Вот код
public static void Main (string[] args)
{
IEnumerable<int> allData = getIntData ();
Console.WriteLine ("Complete Data display");
foreach (var item in allData) {
Console.Write(item);
Console.Write(" | ");
}
Console.WriteLine ();
filterAllDatas (ref allData, getConditions ());
foreach (var item in allData) {
Console.Write(item);
Console.Write(" | ");
}
Console.WriteLine ();
}
static void filterAllDatas(ref IEnumerable<int> data, IEnumerable<Func<int,bool>> conditions)
{
List<int> filteredData = data.ToList ();
List<Task> tasks = new List<Task>();
foreach (var item in data.AsParallel()) {
foreach (var condition in conditions.AsParallel()) {
tasks.Add(Task.Factory.StartNew(() => {
if (condition(item)) {
filteredData.Remove(item);
}
}));
}
}
Task.WaitAll(tasks.ToArray());
data = filteredData.AsEnumerable ();
}
static IEnumerable<Func<int,bool>> getConditions()
{
yield return (a) => { Console.WriteLine("modulo by 2"); return a % 2 == 0;};
yield return (a) => { Console.WriteLine("modulo by 3"); Thread.Sleep(3000); return a % 3 == 0;};
}
static IEnumerable<int> getIntData ()
{
for (int i = 0; i < 10; i++) {
yield return i;
}
}
Здесь просто код отфильтровывает целое число, которое делится на два или три. Теперь, если я удалю этот поток, код сна будет работать отлично, но если я поставлю, то это не так.
Обычно означает без Thread.Sleep, оба условия выполняются 10 раз, например, для каждого числа. Но если я добавлю Thread.Sleep, первое условие выполняется 7 раз, а второе - 13 раз. И из-за этого мало кто пропускает условие. Я пытаюсь отладить, но не получил ничего, что могло бы указать на проблему с моим кодом.
Есть ли хороший способ добиться этого? Как условие фильтрации данных может работать асинхронно и параллельно для повышения производительности?
Код только для демонстрационных целей.
К вашему сведению: в настоящее время я использую Mono с Xamarine studio на машине с Windows.
Пожалуйста, дайте мне знать, если потребуется дополнительная информация.
2 ответа
Сначала вы можете изменить метод getConditions, чтобы увидеть, что происходит внутри:
static IEnumerable<Func<int, bool>> getConditions()
{
yield return (a) => { Console.WriteLine(a + " modulo by 2"); return a % 2 == 0; };
yield return (a) => { Console.WriteLine(a + " modulo by 3"); Thread.Sleep(3000); return a % 3 == 0; };
}
И если вы прекратите захват переменных foreach, это сработает:
static void filterAllDatas(ref IEnumerable<int> data, IEnumerable<Func<int, bool>> conditions)
{
List<int> filteredData = data.ToList();
List<Task> tasks = new List<Task>();
foreach (var item in data.AsParallel())
{
var i = item;
foreach (var condition in conditions.AsParallel())
{
var c = condition;
tasks.Add(Task.Factory.StartNew(() =>
{
if (c(i))
{
filteredData.Remove(i);
}
}));
}
}
Task.WaitAll(tasks.ToArray());
data = filteredData.AsEnumerable();
}
Я предполагаю, что это связано с тем, как лямбда вашей задачи закрывается над переменной цикла condition
, Попробуйте изменить его следующим образом:
foreach (var condition in conditions.AsParallel()) {
var tasksCondition = condition
tasks.Add(Task.Factory.StartNew(() => {
if (tasksCondition(item)) {
filteredData.Remove(item);
}
}));
Обратите внимание, что вы также закрываете переменную цикла item
, что может вызвать аналогичные проблемы.