Как собрать возвращаемые значения из Parallel.ForEach?
Я называю медленный веб-сервис параллельно. Все было замечательно, пока я не понял, что мне нужно получить некоторую информацию от службы. Но я не вижу, где вернуть ценности. Я не могу писать в базу данных, HttpContext.Current представляется нулевым внутри метода с именем Parallel.ForEach
Ниже приведен пример программы (на ваш взгляд, представьте себе медленную веб-службу вместо конкатенации строк)
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
WordMaker m = new WordMaker();
m.MakeIt();
}
public class WordMaker
{
public void MakeIt()
{
string[] words = { "ack", "ook" };
ParallelLoopResult result = Parallel.ForEach(words, word => AddB(word));
Console.WriteLine("Where did my results go?");
Console.ReadKey();
}
public string AddB(string word)
{
return "b" + word;
}
}
}
Ответы
Ответ 1
Вы отбросили его здесь.
ParallelLoopResult result = Parallel.ForEach(words, word => AddB(word));
Вероятно, вам нужно что-то вроде
ParallelLoopResult result = Parallel.ForEach(words, word =>
{
string result = AddB(word);
// do something with result
});
Если вы хотите получить какую-то коллекцию в конце этого, рассмотрите возможность использования одной из коллекций в System.Collections.Concurrent
, например ConcurrentBag
var resultCollection = new ConcurrentBag<string>();
ParallelLoopResult result = Parallel.ForEach(words, word =>
{
resultCollectin.Add(AddB(word));
});
// Do something with result
Ответ 2
Не используйте ConcurrentBag
для сбора результатов, поскольку они чрезвычайно медленны.
Вместо этого используйте локальный замок.
var resultCollection = new List<string>();
object localLockObject = new object();
Parallel.ForEach<string, List<string>>(
words,
() => { return new List<string>(); },
(word, state, localList) =>
{
localList.Add(AddB(word));
return localList;
},
(finalResult) => { lock (localLockObject) resultCollection.AddRange(finalResult); }
);
// Do something with resultCollection here
Ответ 3
Возможно, вы захотите использовать метод расширения AsParallel
IEnumerable
, он позаботится о concurrency для вас и будет собирать результаты.
words.AsParallel().Select(AddB).ToArray()
Синхронизация (например, блокировки или одновременные коллекции, которые используют блокировки), как правило, является узким местом параллельных алгоритмов. Лучше всего избегать синхронизации, насколько это возможно. Я предполагаю, что AsParallel
использует что-то более умное, как помещение всех элементов, созданных в одном потоке, в локальную неконкурентную коллекцию, а затем их объединение в конце.
Ответ 4
Как насчет чего-то вроде этого:
public class WordContainer
{
public WordContainer(string word)
{
Word = word;
}
public string Word { get; private set; }
public string Result { get; set; }
}
public class WordMaker
{
public void MakeIt()
{
string[] words = { "ack", "ook" };
List<WordContainer> containers = words.Select(w => new WordContainer(w)).ToList();
Parallel.ForEach(containers, AddB);
//containers.ForEach(c => Console.WriteLine(c.Result));
foreach (var container in containers)
{
Console.WriteLine(container.Result);
}
Console.ReadKey();
}
public void AddB(WordContainer container)
{
container.Result = "b" + container.Word;
}
}
Я считаю, что блокирующие или параллельные объекты не нужны, если вам не нужны результаты для взаимодействия друг с другом (например, вы вычисляете сумму или комбинируете все слова). В этом случае ForEach аккуратно разбивает ваш первоначальный список и передает каждому потоку свой собственный объект, чтобы он мог манипулировать всем, что он хочет, не беспокоясь о вмешательстве в другие потоки.
Ответ 5
Это кажется безопасным, быстрым и простым:
public string[] MakeIt() {
string[] words = { "ack", "ook" };
string[] results = new string[words.Length];
ParallelLoopResult result =
Parallel.For(0, words.Length, i => results[i] = AddB(words[i]));
return results;
}