Асинхронно генерировать и добавлять POCO в List<T>

Я выполняю запрос, который возвращает примерно 11 000 строк. Каждая из этих строк сопоставлена ​​с уникальным POCO, для создания которого я написал облегченную ORM, называемую ToCustomObject<T>. Прямо сейчас у меня уже есть асинхронный метод, который получает результаты этого запроса и помещает их в DataTable. Затем я перебираю все строки и вызываю для них ToCustomObject, чтобы сгенерировать свой объект, а затем добавляю его в List<T>. Сам запрос занимает всего полсекунды, но цикл генерации всех моих POCO занимает несколько секунд.

public async Task<List<Foo>> GetFoosAsync()
{
    //empty list to populate and return
    List<Foo> foos = [];

    //example, this is not how the query actually executes, do not need assistance with this part
    string query = "select t.Field1, t.Field2 from Table";
    DataTable results = await customDbHandlerThatsNotRelevant.ExecuteQueryAsync(query);

    //async way to handle this?
    foreach (DataRow row in results.Rows)
        foos.Add(row.ToCustomObject<Foo>());

    return foos ;
}

Я думал о том, чтобы использовать Task.WhenAll(), чтобы поместить все поколения в список задач и выполнить таким образом, но foos.Add() не ожидается. Я также изучал Parallel.ForEach(), но я не настолько знаком с этим и, насколько я могу судить, не смог бы выполнить то, что пытаюсь сделать.

TL;DR: Как я могу сделать так, чтобы все появления foos.Add(row.ToCustomObject<Foo>()); происходили асинхронно?

List<T> не является потокобезопасным. Одновременные вызовы Add() просто разорвут список и приведут к сбою приложения. Можете ли вы рассмотреть какую-нибудь другую (одновременную) коллекцию?
Serg 03.08.2024 03:22

Почему ToCustomObject так медленно? POCO должны быть простыми и легкими. Вы выполняете запросы для каждого POCO?

Theodor Zoulias 03.08.2024 12:55

Отражение, если его использовать не очень осторожно, скорее всего, будет работать очень плохо. Вам нужно заставить эту реализацию работать гораздо эффективнее, а не пытаться распараллелить крайне неэффективную реализацию.

Servy 03.08.2024 18:01

Загрузите «результаты» в ConcurrentQueue. Используйте дополнительные «воркеры»/задачи для исключения из очереди; процесс; и добавьте (не в список) целевую параллельную коллекцию: например. ConcurrentDictionary, Stack, Bag или другая очередь; затем перейдите к наблюдаемой коллекции.

Gerry Schmitz 03.08.2024 20:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
85
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Прежде всего, я рекомендую использовать .NET Dapper, он делает именно то, что вам нужно: https://www.nuget.org/packages/Dapper

Я использую этот пакет уже несколько лет и являюсь его поклонником.

Вы можете использовать Parallel.ForEach следующим образом:

ConcurrentBag<Foo> foos = new ConcurrentBag<Foo>();

Parallel.ForEach(results.Rows, row =>
   {
        foos.Add(row.ToCustomObject<Foo>());
   }
);

При добавлении объектов в список в нескольких потоках вам необходимо использовать потокобезопасный объект, например. одновременный пакет является потокобезопасным.

Не существует абсолютно никакого способа не утонуть в коде синхронизации потоков по сравнению с реальной выполняемой работой.

Blindy 03.08.2024 03:54

Запрос к базе данных отсутствует, это правильно. Вместо этого я написал, чтобы порекомендовать Dapper, и дал ссылку на него. Пожалуйста, объясните, почему цикл не должен работать.

Sebastian S. 04.08.2024 08:31
Ответ принят как подходящий

Ну это сильно зависит. Видите ли вы в этом цикле узкое место в производительности? Если да, то возможно стоит распараллелить.

Но, как правило, он не предназначен для асинхронного выполнения, поскольку ваш метод ToCustomObject не является асинхронным, поэтому вам придется заключать каждое выполнение в Task, что уже является накладными расходами. Если вы сделаете это для всех элементов списка, ваша производительность может даже снизиться!

НО, если вы видите проблемы с производительностью, я могу предложить несколько способов:

  1. упаковка ToCustomObject звоню Task, создаю Task для каждого предмета и ожидаю их всех одновременно
  2. используйте Parallel.ForEach
  3. если ToCustomObject работает плохо, возможно, внутри него есть какой-то синхронный код, который можно выполнить асинхронно? Тогда вы могли бы сделать асинхронную версию ToCustomObject

Конечно, вам нужно позаботиться о потенциальном доступе к вашему List из нескольких потоков, нужно ли вам использовать некоторую синхронизацию или потокобезопасную коллекцию.

Опять же, если он содержит или может содержать асинхронную операцию, конечно, сделайте его асинхронным, но если он синхронный, это не имеет смысла.

Michał Turczyn 03.08.2024 17:50

Кажется, что большинство моих проблем с производительностью на самом деле просто ждали, пока страница Swagger загрузит все результаты. Тестирование с помощью Postmans привело к возврату всего за 1 секунду. Тем не менее, Parallel.Foreach даже на этом сэкономил неплохой процент, так что я буду считать это принятым.

Austin 05.08.2024 18:55

Другие вопросы по теме