Обложка канала

codaza

206 @codaza

Канал о разработке на платформе .NET с использованием языка программирования C#. Рассматриваются актуальные подходы и современные методологии разработки.

codaza

4 года назад
Открыть в
#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 можно получить гораздо больше ценных и практических знаний нежели из очередной статьи в интернете. Всем активным участникам огромное спасибо за вклад! 💙