Parallel.ForEach дублирует элементы в BlockingCollection

Разрабатываю текстовый поиск товаров по коллекции в памяти. Сам поиск не простой, учитывается множество вариантов написания. Есть возможность поиска нескольких товаров за раз, разделив их через запятую или другой разделитель. Поэтому, было решено распараллелить поиск при помощи Parallel.ForEach

Проблема заключается в том, что иногда результирующая коллекция возвращает дубликаты:

введите сюда описание изображения

При первом поиске после старта приложения всё норм (скриншот слева). После второго поиска вышли дубликаты (скриншот справа)

public partial class SearchEngine<T> 
{
    public List<T> OriginalCollection { get; set; }

    public List<T> SearchParallel(string searchText, SearchEngineOptions options)
    {
            if (string.IsNullOrEmpty(searchText?.Trim()))
                return OriginalCollection;

            //List<T> result = new List<T>();
            BlockingCollection<T> result = new BlockingCollection<T>();

            if (options == null)
            {
                options = new SearchEngineOptions()
                {
                    Separators = new List<char>() { ',', '\n' }
                };
            }

            //разделить строки для множественного поиска
             var splitText = searchText
                .Split(options.Separators.ToArray())
                .Where(w => !string.IsNullOrEmpty(w?.Trim()))
                .Distinct();

            //установка ограничения в 20 элементов за раз
            if (splitText.Length > 20)
                splitText = splitText
                    .Take(20)
                    .ToArray();

            if (splitText.Count() == 1)
            {
                return Search(searchText);
            }

            var parallelOptions = new ParallelOptions()
            {
               //MaxDegreeOfParallelism = 4
            };

            Parallel.ForEach(splitText, parallelOptions, (s) =>
            {
                var searchResult = Search(s);

                foreach (var item in searchResult)
                {
                    result.TryAdd(item);
                }
            });

            return result.ToList();
      }

      public List<T> Search(string searchText)
      {
         //поиск товаров
      }
}

Использую потокобезопасную коллекцию BlockingCollection, но увы не работает.

Платформа .NET 4.6.1

Какие еще нюансы нужно учитывать при параллельном поиске?


Ответы (1 шт):

Автор решения: tym32167

Использую потокобезопасную коллекцию BlockingCollection, но увы не работает. У вас не работает ваша логика в коде.

  1. Что за текст вы в него передаете?
  2. Что вы делаете внутри public List<T> Search(string searchText)?
  3. Зачем вам вообще BlockingCollection?
  4. Зачем тут Parallel.ForEach ?
  5. Что это и зачем тут это OriginalCollection ?

Ваш код можно переписать так

public partial class SearchEngine<T>
{
    public List<T> OriginalCollection { get; set; }

    public List<T> SearchParallel(string searchText, SearchEngineOptions options)
    {
        if (string.IsNullOrEmpty(searchText?.Trim()))
            return OriginalCollection;
        
        var splitText = SplitSearchText(searchText, options);
        
        return  splitText
                    .AsParallel()
                    // .WithDegreeOfParallelism(4)
                    .SelectMany(x=>Search(x))
                    .Distinct()
                    .ToList();  
    }
    
    IEnumerable<string> SplitSearchText(string searchText, SearchEngineOptions options)
    {
        //разделить строки для множественного поиска
        return searchText
           .Split(options.Separators.ToArray(), StringSplitOptions.RemoveEmptyEntries)
           .Where(w => !string.IsNullOrEmpty(w?.Trim()))
           .Distinct()
           .Take(20);   
    }

    public List<T> Search(string searchText)
    {
        //поиск товаров
    }
}
→ Ссылка