Parallel.ForEach с количеством
От: _NN_ www.nemerleweb.com
Дата: 19.09.22 05:45
Оценка:
Имеется код вида:

List<Task> tasks;

foreach (var element in enumerable)
{
  tasks.Add(Task.Run( () => {} ) );
  Logger.Info($"Running task {element}");
}

Logger.Info($"Started {tasks.Count} tasks");
Task.WaitAll(tasks.ToArray());


Недостаток очевиден, мы создаём таск на каждый элемент, и лучше использовать Parallel.ForEach.
Однако с помощью Parallel.ForEach невозможно получить количество элементов.
В вышеприведённом примере мы их получаем неявно за счёт использования List.

Как вариант можно хранить переменную и автономно увеличивать:

int count = 0;

Parallel.ForEach(enumerable, element =>
{
  Interlocked.Increment(ref count);
  Logger.Info($"Running task {element}");
});

Logger.Info($"Run {count} tasks");


Тогда мы получаем лишний расход в виде Interlocked.Increment и выведем лог только после того как вся работа окончится в отличии от начального кода.

Как вы решаете эту задачу ?
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: Parallel.ForEach с количеством
От: vaa  
Дата: 19.09.22 06:05
Оценка:
Здравствуйте, _NN_, Вы писали:


_NN>Как вы решаете эту задачу ?

не совсем понятно зачем, но может это поможет?
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallelloopstate?view=net-6.0
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re: Parallel.ForEach с количеством
От: Sharowarsheg  
Дата: 19.09.22 07:27
Оценка: +1
Здравствуйте, _NN_, Вы писали:


_NN>Тогда мы получаем лишний расход в виде Interlocked.Increment и выведем лог только после того как вся работа окончится в отличии от начального кода.


Interlocked.Increment дешевле, чем таски создавать. И вообще Interlocked.Increment — довольно дешёвая штука. Если таски настолько короткие, что начинает играть роль лишний Interlocked.Increment, и вообще, если ты его замечаешь, то пора переходить к частным решениям конкретно для этого места в конкретном коде.
Re: Parallel.ForEach с количеством
От: kov_serg Россия  
Дата: 19.09.22 08:24
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Как вы решаете эту задачу ?

int count = enumerable.Count;
Logger.Info($"Run {count} tasks");


Re[2]: Parallel.ForEach с количеством
От: _NN_ www.nemerleweb.com
Дата: 19.09.22 08:43
Оценка: +1
Здравствуйте, kov_serg, Вы писали:

_>Здравствуйте, _NN_, Вы писали:


_NN>>Как вы решаете эту задачу ?

_>
_>int count = enumerable.Count;
_>Logger.Info($"Run {count} tasks");
_>


У IEnumerable enumerable нет Count, иначе не было бы вопроса.
А вызов Enumerable.Count() приведёт к двойному обходу перечисления, что тоже не подходит.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: Parallel.ForEach с количеством
От: karbofos42 Россия  
Дата: 19.09.22 08:50
Оценка: 11 (1) +2
Здравствуйте, _NN_, Вы писали:

_NN>Как вы решаете эту задачу ?


Какую задачу?
В исходном коде до завершения обработки выводится отладочная инфа о числе созданных тасков (не знаю что это даёт правда).
В предложенном изменении сначала выполняется обработка всех элементов, а потом выводится в лог информация о тасках,
хотя вроде и смысл изменений в том, чтобы таски не создавать и по факту это просто число обрабатываемых элементов.
Задача в итоге чисто вывести что-то в лог или в чём-то другом?
Re[3]: Parallel.ForEach с количеством
От: kov_serg Россия  
Дата: 19.09.22 10:08
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>У IEnumerable enumerable нет Count, иначе не было бы вопроса.

Что мешает добавить в свой интерфейс с количеством элементов.
Вы же это список заполняли как-то.

_NN>А вызов Enumerable.Count() приведёт к двойному обходу перечисления, что тоже не подходит.

Почему? Пока идёт выполнение ваших задач, что мешает пройтись по списку еще раз? (Кроме религиозных соображений)
Re[4]: Parallel.ForEach с количеством
От: _NN_ www.nemerleweb.com
Дата: 19.09.22 10:13
Оценка: +1
Здравствуйте, kov_serg, Вы писали:

_>Здравствуйте, _NN_, Вы писали:


_NN>>У IEnumerable enumerable нет Count, иначе не было бы вопроса.

_>Что мешает добавить в свой интерфейс с количеством элементов.
_>Вы же это список заполняли как-то.

Ну к примеру у меня LINQ выражение какое-нибудь.

_NN>>А вызов Enumerable.Count() приведёт к двойному обходу перечисления, что тоже не подходит.

_>Почему? Пока идёт выполнение ваших задач, что мешает пройтись по списку еще раз? (Кроме религиозных соображений)

Это если такая возможность есть.
Не каждый IEnumerable даст пройтись два раза без последствий.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[5]: Parallel.ForEach с количеством
От: kov_serg Россия  
Дата: 19.09.22 10:57
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Ну к примеру у меня LINQ выражение какое-нибудь.

И что вам мешает реализовать несколько интерфейсов?

_NN>Это если такая возможность есть.

_NN>Не каждый IEnumerable даст пройтись два раза без последствий.
Это уже кривые реализации. В IEnumerable есть который должен переходить в начало.
IEnumerable.Reset
Re[6]: Parallel.ForEach с количеством
От: _NN_ www.nemerleweb.com
Дата: 19.09.22 11:18
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Здравствуйте, _NN_, Вы писали:


_NN>>Ну к примеру у меня LINQ выражение какое-нибудь.

_>И что вам мешает реализовать несколько интерфейсов?

Ну например у меня энумератор отправляет данные на сервер и ждёт ответа, а повторный проход даст другой результат.
А в руках у меня только IEnumerable.
Да что угодно может быть.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[7]: Parallel.ForEach с количеством
От: kov_serg Россия  
Дата: 19.09.22 11:33
Оценка:
Здравствуйте, _NN_, Вы писали:


_>>И что вам мешает реализовать несколько интерфейсов?


_NN>Ну например у меня энумератор отправляет данные на сервер и ждёт ответа, а повторный проход даст другой результат.

Что мешает от сервера получить количество элементов?
_NN>А в руках у меня только IEnumerable.
Сделайте копию.
_NN>Да что угодно может быть.
Так и исключения можно кидать при переходе к следующему элементу.

https://youtu.be/uK7pDCYslDA?t=392
Re[3]: Parallel.ForEach с количеством
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 19.09.22 14:30
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>У IEnumerable enumerable нет Count, иначе не было бы вопроса.

Зависит от того, кто этот IEnumerable представляет. Для ICollection возьмет из Count
https://github.com/microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs
public static int Count<TSource>(this IEnumerable<TSource> source) {
            if (source == null) throw Error.ArgumentNull("source");
            ICollection<TSource> collectionoft = source as ICollection<TSource>;
            if (collectionoft != null) return collectionoft.Count;
            ICollection collection = source as ICollection;
            if (collection != null) return collection.Count;
            int count = 0;
            using (IEnumerator<TSource> e = source.GetEnumerator()) {
                checked {
                    while (e.MoveNext()) count++;
                }
            }
            return count;
        }
и солнце б утром не вставало, когда бы не было меня
Re: Parallel.ForEach с количеством
От: ltc  
Дата: 19.09.22 15:28
Оценка: 16 (2) +1
Здравствуйте, _NN_, Вы писали:

_NN>Тогда мы получаем лишний расход в виде Interlocked.Increment и выведем лог только после того как вся работа окончится в отличии от начального кода.


Interlocked.Increment это вообще мелочи, можно пренебречь. Внутри Parallel.ForEach тоже свои Interlocked.Increment есть, если уж на то пошло.

По поводу лога — если уж очень хочется, чтобы логировал как можно раньше, то сделай враппер для своей enumerable, который на последнем MoveNext будет сразу счётчик в лог писать.
Re[7]: Parallel.ForEach с количеством
От: Sharowarsheg  
Дата: 19.09.22 15:35
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Ну например у меня энумератор отправляет данные на сервер и ждёт ответа, а повторный проход даст другой результат.

_NN>А в руках у меня только IEnumerable.
_NN>Да что угодно может быть.

С такой постановкой исходный вопрос вообще не имеет значения. Делай хоть так, хоть эдак — на скорость не влияет. И Interlocked.Increment, и создание тасков вместе с последующим GC, в обычном случае настолько быстрее, чем запрос к серверу на каждый enumerable, что разницу нельзя будет заметить.
Re[4]: Parallel.ForEach с количеством
От: ltc  
Дата: 19.09.22 15:45
Оценка:
Здравствуйте, kov_serg, Вы писали:

_NN>>У IEnumerable enumerable нет Count, иначе не было бы вопроса.

_>Что мешает добавить в свой интерфейс с количеством элементов.
_>Вы же это список заполняли как-то.

Это абстракция IEnumerable. Количество элементов никому не известно на момент получения первых элементов. Иначе это был бы ICollection.

_NN>>А вызов Enumerable.Count() приведёт к двойному обходу перечисления, что тоже не подходит.

_>Почему? Пока идёт выполнение ваших задач, что мешает пройтись по списку еще раз? (Кроме религиозных соображений)

Начать с того, что это не список. Повторный проход по IEnumerable может вызвать повторный вызов SELECT * FROM, например. С совершенно иным результатом.
Re: Parallel.ForEach с количеством
От: _NN_ www.nemerleweb.com
Дата: 19.09.22 20:04
Оценка:
Всем спасибо за мысли и советы.
На данный момент проще привести всё заранее к массиву так как количество не превысит нескольких тысяч.
А вот создавать тысячи таксов это гораздо хуже.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: Parallel.ForEach с количеством
От: IT Россия linq2db.com
Дата: 19.09.22 20:21
Оценка: 6 (1) +1
Здравствуйте, _NN_, Вы писали:

int count = 0;

Parallel.ForEach(enumerable.Select(i => { _count++, return i; }), element =>
{
  Logger.Info($"Running task {element}");
});

Logger.Info($"Run {count} tasks");
Если нам не помогут, то мы тоже никого не пощадим.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.