2

Приведу три примера выборки из коллекций с использованием PlINQ. 1 Пример

//List<MDRItem> startMdrItems, List<SDSItem> sdsItemsGlobal это коллекции кастомных классов
//передаются в метод в качестве параметров
 var mdrFiltered = (from mdrItem in startMdrItems.AsParallel()
                               from sdsItem in sdsItemsGlobal.AsParallel()
                               where mdrItem.TitleNumber == sdsItem.TitleNumber && sdsItem.TitleStatus == "Активный"
                               select mdrItem
                              ).ToList();

2 пример

//List<TSRItem> tsrFiltered, List<SDSItem> sdsItems это коллекции кастомных классов

var tsrFilteredSecond = (from tsrItem in tsrFiltered.AsParallel() from sdsItem in sdsItems.AsParallel() where tsrItem.TitleNumber == sdsItem.TitleNumber && sdsItem.TitleStatus == "Активный" select new TSRItem() {

                                     RowIdx = tsrItem.RowIdx,
                                     FileName = tsrItem.FileName,
                                     ProjectFromSchedule = tsrItem.ProjectFromSchedule,
                                     ProjectName = tsrItem.ProjectName,
                                     GosFinSign = sdsItem.GosFinSign
                                 }
                                );

3 пример

//List<TSRItem> tsrFilteredSecond коллекция кастомного класса
var listToForeach = new ConcurrentBag<TSRItem>(tsrFilteredSecond);
ConcurrentBag<TSRItem> filterdTsrItems = new ConcurrentBag<TSRItem>();
listToForeach.AsParallel().ForAll/*.ForEach*/(tsr =>
            {
                var chekedTitleNumber = String.IsNullOrEmpty(tsr.TitleNumber) ? "" : tsr.TitleNumber;
                var chekedMark = String.IsNullOrEmpty(tsr.Mark) ? "" : string.Concat(":", tsr.Mark);
            tsr.MarkSMGenerated = GenerateMarkSM(tsr.PurchaseSpecification);
            var checkedMarkSm = String.IsNullOrEmpty(tsr.MarkSMGenerated) ? &quot;&quot; : string.Concat(&quot;:&quot;, tsr.MarkSMGenerated);

            tsr.NomenclatureGroupMTOGenerated = GenerateGroupMTO(tsr);
            var checkedNomenclatureGroupMTOGenerated = String.IsNullOrEmpty(tsr.NomenclatureGroupMTOGenerated) ? &quot;&quot; : string.Concat(&quot;:&quot;, tsr.NomenclatureGroupMTOGenerated);

            tsr.BuildingcodeGenerated = GenerateBulidingCode(tsr);
            var checkedBuildingCode = String.IsNullOrEmpty(tsr.BuildingcodeGenerated) ? &quot;&quot; : string.Concat(&quot;:&quot;, tsr.BuildingcodeGenerated);

            tsr.GeneratedKey = String.Format(&quot;{0}{1}{2}{3}{4}&quot;, chekedTitleNumber, chekedMark, checkedMarkSm, checkedBuildingCode, checkedNomenclatureGroupMTOGenerated);


            filterdTsrItems.Add(tsr);

        });

Вопрос следующий: Нужно ли при выполнении таких переборов объявлять потокобезопасные коллекции(например использовать ConcurrentBag вместо List как в 3 м примере)? Т.е. для первого примера использовать ConcurrentBag startMdrItems, ConcurrentBag sdsItemsGlobal, для второго ConcurrentBag tsrFiltered, ConcurrentBag sdsItems. Выявил проблему с потокобезопасностостью в 3 примере в строчке filterdTsrItems.Add(tsr); когда filterdTsrItems был объявлен через List<> а не через ConcurrentBag<>, отсюда и возникли сомнения по поводу остальных примеров.

  • По поводу закомментированного ForEach: Prefer ForAll to ForEach when it is possible. ForAll в данном случае действительно лучше. Но только при использовании потокобезопасной коллекции внутри! А если взять ForEach, то коллекциию можно использовать любую. – Alexander Petrov Nov 25 '22 at 08:41

1 Answers1

2

При наличии чтения и отсутствии записи совершенно безопасно иметь доступ к коллекции из нескольких потоков. Познакомьтесь с концепцией Reader/Writer Lock. Она говорит о том, что писать может только один, а читать могут сколько угодно потоков, но только пока никто не пишет.

  • Первый пример - запись в mdrFiltered происходит в одном потоке, всё ок
  • Второй пример - tsrFilteredSecond это не материализованный запрос, а перечислитель, запрос выполнится позже
  • Третий пример - filterdTsrItems ок как потокобезопасная коллекция, listToForeach не ок, так как эта коллекция только для чтения и для нее не требуется какая-либо потокобезопасность

Совет: используйте массивы вместо списков там, где не надо модифицировать состав коллекции, это полезно для производительности кода в некоторых случаях. Если нужно для потокобезопасности снять снапшот с какого-то непотокобезопасного источника данных в многопоточной среде, спокойно используйте .ToArray() на источнике данных под локом в случае если возможна конкурентная запись. Если же запись в коллекцию во время ее перечисления невозможна, можно спокойно обойтись без снапшотов и перечислять её саму.

aepot
  • 49,560
  • Уточняющий вопрос: по 3 примеру: при переборе listToForeach я записываю свойства каждого экземпляра tsr (например tsr.GeneratedKey=....), разве здесь нет записи – Antonov Konstantin Nov 25 '22 at 07:43
  • 1
    @AntonovKonstantin но ведь не к списку обращаетесь, а к членам его элемента. Сам список при этом только читается. – aepot Nov 25 '22 at 07:45
  • и по второму примеру если запрос будет выглядеть var tsrFilteredSecond =(... все тоже самое).ToArray() или .ToList()? – Antonov Konstantin Nov 25 '22 at 07:47
  • 1
    @AntonovKonstantin .ToArray() и .ToList() в плане потокобезопасности не отличаются ничем. – aepot Nov 25 '22 at 07:48