Подтвердить что ты не робот

Как собрать возвращаемые значения из 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;
        }
    }

}
4b9b3361

Ответ 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;
    }