codaza
5.34K subscribers
41 photos
27 links
Канал о разработке на платформе .NET с использованием языка программирования C#. Рассматриваются актуальные подходы и современные методологии разработки.

YouTube:
https://www.youtube.com/c/codaza-channel

Контакты:
codaza.channel@gmail.com
Download Telegram
#codaza_отвечает

С кодом из предыдущего поста не всё ок. Ему не хватает асинхронности. Если не ожидать результат выполнения метода GetStringAsync() у объекта client (строка 5), то его освобождение из памяти произойдет раньше, чем выполнится метод, так как мы используем using (строка 3). Поэтому, перед dispose объекта client, нам необходимо дождаться результата выполнения метода, а не просто возвращать task.

Подписчик Aidar дал точный ответ в комментариях 👍
#codaza_отвечает

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

Где же был зарыт "главный слон" 🐘? Проблема, с которой данный код никак нельзя было допускать в production-среду. Проблема была на строках 12-13:

int keyWordIndex = dataSlice.Value.TextFraction.Substring(actualPosition).IndexOf(keyWord);

Вызов метода Substring() - вот основная проблема. Дело в том, что при каждом вызове этого метода, в памяти будет создаваться полная копия строки, содержащаяся в свойстве TextFraction. Это не проблема при нечастных вызовах на небольших строках и без использования циклов. Для нашей ситуации это совершенно не годится, так как методу предстоит работать с большим массивом данных. Только представьте что может произойти с оперативной памятью при частых вызовах метода ExtractStochasticDataAsync(). И первым проблему заметил Александр Максименко, указав на то что вместо вызова Substring() следует делать вызов метода расширения для класса String - AsSpan(). Такой подход позволит не создавать копию строки TextFraction в оперативной памяти, что существенно снизит нагрузку на неё.

Далее, Александр увидел возможность для параллельной обработки данных в словаре dataSlices. При параллельной обработке нужно учитывать проблемы совместного доступа, поэтому, для словаря indexes, вместо Dictionary<int, int>, Александр взял потокобезопасную коллекцию ConcurrentDictionary<int, int>.

Метод ExtractStochasticDataAsync() является асинхронным и хорошей практикой является предоставления возможности для остановки метода (тем более метод работает в цикле с большим объемом данных). На эту проблему обратил внимание Aleksandr Radchenko, предложив использование CancellationToken.

Итоговый вариант после рефакторинга стал выглядеть так:

public async Task<IDictionary<int, int>> ExtractStochasticDataAsync(string keyWord, CancellationToken ct = default)
{
IDictionary<int, AnalyticalDataSlice> dataSlices = await LoadAnalyticalDataSlicesAsync(ct);

ConcurrentDictionary<int, int> indexes = new();

var tasks = dataSlices.Select(async ds =>
{
ct.ThrowIfCancellationRequested();

int actualPosition = await LoadActualPositionAsync(ct);

int keyWordIndex = ds.Value.TextFraction
.AsSpan(actualPosition).IndexOf(keyWord);

indexes.TryAdd(ds.Key, keyWordIndex + actualPosition);
});

await Task.WhenAll(tasks);

return indexes;
}

Кроме того, Aleksandr Radchenko предложил еще ряд важных улучшений с примерами листингов (за что ему отдельное спасибо). Например, использовать в качестве возвращаемого значения метода ExtractStochasticDataAsync() асинхронную реализацию интерфейса IAsyncEnumerable и оператор yield, что позволит организовать асинхронную обработку данных из вызываемого метода. Это потребует изменение выходного API у метода ExtractStochasticDataAsync(), но если это возможно по имеющимся бизнес-требованиям, сделать это точно стоит. Этот вариант будет выглядеть так:

public async IAsyncEnumerable<(int Key, int Position)> ExtractStochasticDataAsync01(string keyWord, [EnumeratorCancellation] CancellationToken ct = default)
{
IDictionary<int, AnaliticalDataSlice> dataSlices = await LoadAnalyticalDataSlicesAsync(ct);

foreach(var dataSlice in dataSlices)
{
int actualPosition = await LoadActualPositionAsync(ct);

int keyWordIndex = dataSlice.Value.TextFraction
.AsSpan(actualPosition).IndexOf(keyWord);

ct.ThrowIfCancellationRequested();

yield return (dataSlice.Key, keyWordIndex + actualPosition);
};
}

Из таких code review можно получить гораздо больше ценных и практических знаний нежели из очередной статьи в интернете.

Всем активным участникам огромное спасибо за вклад! 💙