Член C#, чтобы проверить свойство на нулевое значение и при необходимости установить его с помощью асинхронной функции?

Это НЕ провал чего-либо. Я просто пытаюсь определить, как писать на С# более элегантно.

Я обнаружил, что в проекте C# я делаю очень много

if (null != classX.user || await classX.getUserAsync())
{
     // getUserAsync() either sets classX.user appropriately and returns true, or else returns false.
     // Calls to classX members and other functions that are only OK when classX.user is valid follow

и я хотел бы объединить это в функцию-член, например

private async Task<bool> IsUserSet()
{
     if (null != user)
         return await Task.FromResult<bool>(user);
     
     return await getUserAsync();
}

потому что (ИМХО) замена оператора if на if (IsUserSet()) более читабельна.

Тем не менее, мне интересно, стоит ли снижение производительности (есть один с вставкой другой задачи, не так ли?) или есть способ сделать это без вставки другой задачи. (Или, может быть, мне просто нужно немного узнать, как писать более чистый код ASP.NET!)

*** ОКОНЧАТЕЛЬНОЕ редактирование после многого из всех комментариев:

  • Мне следует рассмотреть ValueTask для любых промежуточных функций Task. (или, когда это возможно), это может сократить накладные расходы (спасибо Charlieface).
  • Используя ValueTask в SetUserAsync (был GetUserAsync), я могу включить проверку «null» внутри функции SetUserAsync вместо создания промежуточной функции (Акаш, Педро).
  • В общем, мне не обязательно возвращаться Task.FromResult<> (я не знаю, где я подхватил эту привычку) это добавляет ненужные накладные расходы (фузи).
if (classX.user ?? await classX.getUserAsync()) не сработает, потому что classX.user должен быть типа bool?, и я бы поставил деньги на то, чтобы он был другого типа.
phuzi 24.07.2024 16:39

Почему бы просто не всегда звонить await classX.getUserAsync()? Разве не достаточно умно сначала проверить classX.user?

Heretic Monkey 24.07.2024 16:40
getUserAsync() имя должно измениться на setUserAsync(), а условие user is null должно находиться под setUserAsync(). так что вам остаётся только проверить if (classX.user is not null)
Akash Kansara 24.07.2024 16:44

phuzi - спасибо, удалил мое плохое кодирование с помощью classX.user Heretic Monkey - Вы правы, но меня все еще интересует вопрос производительности, сильно ли снижается производительность при вызове этой асинхронной функции Task<bool> и ее использовании проверить наличие значения null или лучше проверить наличие значения null перед вызовом getUserAsync и созданием связанной с ним задачи?

Greg L. 24.07.2024 16:45

@GregL. производительность будет одинаковой независимо от того, проверяете ли вы спереди или внутри getUserAsync()

Akash Kansara 24.07.2024 16:47

Спасибо, Акаш... Я думал, что из-за таких тем, как reddit.com/r/csharp/comments/18gjudv/… Мне следует избегать ненужных вызовов асинхронных функций, проверяя значение null перед вызовом... это не так тогда большое дело?

Greg L. 24.07.2024 16:57

Какой код обычно входит в ветку else этого if? Это какая-то распространенная обработка ошибок? Что произойдет, если у меня нет пользователя и я не смогу решить эту проблему?

Zdeněk Jelínek 24.07.2024 17:14

Это не ошибка, но этот код рассматривает пользователя как анонимного (неаутентифицированного/неизвестного) пользователя.

Greg L. 24.07.2024 17:19

ValueTask позволит избежать попадания в перформанс, если user уже получен.

Charlieface 24.07.2024 18:10

Charlieface: Хорошо, я не знал о ValueTask, я начинаю читать о нем, похоже, мне следует включить нулевую проверку в SetUserAsync() и использовать ValueTask.

Greg L. 24.07.2024 18:22

Если «пользователь не равен нулю», почему вам все еще нужен асинхронный вызов для получения «находящегося там пользователя»? Это почти подразумевает состояние гонки: пользователь может исчезнуть.

Gerry Schmitz 24.07.2024 19:47
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
11
70
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Я думаю, ты был почти там с

private async Task<bool> IsUserSet()
{
     if (null != user)
         return await Task.FromResult<bool>(user);
     
     return await getUserAsync();
}

но return await Task.FromResult<bool>(user); должно быть return true;

Вы можете упростить это и сделать что-то похожее на исходный код.

private async Task<bool> IsUserSet()
{
     return user is not null || await getUserAsync();
}

Примечание на странице await Task.FromResult()

Когда вы это сделаете await Task.FromResult<bool>(user), у вас будет лишний код.

Task.FromResult<bool>(user) оборачивает user в завершенную задачу и префикс await автоматически разворачивает ее, поэтому await Task.FromResult<bool>(user) можно сократить до user, поскольку на самом деле вы не возвращали Task<bool>, но bool все время и помечаете метод как async автоматически оборачивает возвращаемое значение в задачу`

Спасибо... Я не понимаю, когда использовать Task.FromResult, а не просто конечное значение. Я подумал, что лучше всего вернуться с FromResult для функций, объявленных с помощью Task?

Greg L. 24.07.2024 16:49

@GregL. Я добавил немного await Task.FromResult() к своему ответу.

phuzi 24.07.2024 16:57

Спасибо, Фузи, я возвращаюсь к коду и удаляю некоторые Task.FromResults. Я пытался проверить ваш комментарий, но у меня нет такой репутации, чтобы сделать это.

Greg L. 24.07.2024 17:18

Не стесняйтесь также проголосовать за ответ 😉

phuzi 25.07.2024 15:11

Проголосовал за! Спасибо!

Greg L. 25.07.2024 15:32

Я бы дал читабельный ответ - в конце концов, код, написанный так, чтобы его было легко понять, является самым элегантным из всех :)

private async Task<bool> IsUserSet()
{
    if (user == null)
    {
        return false;
    }
    else
    {
        return await getUserAsync();
    }
}

Говоря об этом, я бы, вероятно, прокомментировал выбор имени для getUserAsync(), поскольку на самом деле он не возвращает пользователя. Правильный выбор имени сделал бы код более читабельным.

Педро: Да, я вижу, что вы правы, как и Акаш прокомментировал выше. Я меняю его на setUserAsync. Спасибо за пример, это звучит правильно, хотя мне все еще интересно вставить еще одну асинхронную задачу, поскольку, видимо, ReSharper это не очень нравится.

Greg L. 24.07.2024 17:23

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

Pedro Costa 24.07.2024 17:38

Перейдем к сути вопроса:

Тем не менее, мне интересно, стоит ли снижение производительности (есть один с вставкой другой задачи, не так ли?) или есть способ сделать это без вставки другой задачи. (Или, может быть, мне просто нужно немного узнать, как писать более чистый код ASP.NET!)

Общая рекомендация — использовать Task, если только вы не столкнулись со сценарием, критичным к производительности, где его использование становится (или может стать) проблемой. Да, с Tasks есть небольшие накладные расходы, но это .NET, практически со всем есть некоторые накладные расходы. Если вы пишете базовую структуру или библиотеку, ориентированную на производительность, то обязательно подумайте об этих темах, но в этих проектах обычно уже есть некоторые рекомендации о том, где и как важна производительность.

Чтобы объяснить немного дальше, по сути, Task выделения состоят из двух частей:

  • Сам объект Task
  • Асинхронный конечный автомат, представляющий метод async в случае, когда await действительно вызывает приостановку.

В некоторых случаях выделения объекта Task можно избежать. При синхронном возврате Task существует синглтон Task.CompletedTask, который можно использовать повторно. Однако существует также кэширование для определенных сценариев, таких как Task<bool> — синхронно завершенный Task<bool> может содержать либо true, либо false, поэтому среда выполнения кэширует два экземпляра. Поскольку рассматриваемый сценарий работает с Task<bool>, синхронный путь по существу не требует выделения ресурсов, и это не является проблемой.

Асинхронный конечный автомат выделяется в случае приостановки. Это когда метод не завершается синхронно (т. е. потому, что выполняется фактический ввод-вывод, например отправка HTTP-запроса). Среде выполнения необходимо хранить локальные значения и продолжение в куче, чтобы она могла продолжить работу в том же логическом контексте после завершения асинхронной операции. Таким образом, в этом случае распределения не избежать.

Некоторые распределения конечных автоматов можно исключить, не используя асинхронные методы, когда await не требуется, например.

// Before: async
public async Task DoWorkAsync()
{
    DoSomething();
    DoSomethingElse();
    await DoSomethingAsync();
}

// After: no async, return task from callee
public Task DoWorkAsync()
{
    DoSomething();
    DoSomethingElse();
    return DoSomethingAsync();
}

Однако это приводит к тому, что эти методы больше не присутствуют в трассировках стека, и это не следует недооценивать.

Также существует понятие ValueTask и ValueTask<T>. Это структуры, поэтому их создание не распределяется. Тем не менее, они по-прежнему выделяют блок конечного автомата при приостановке асинхронного метода, и он немного больше, чем тот, который был создан для Task. Есть случай, когда этого также можно избежать с помощью ValueTask, который использует IValueTaskSource для объединения хранилища асинхронного состояния. Это действительно сложно и применимо не везде.

Итак, подведем итог:

Примитивный Выделяет при синхронном завершении Распределяет при приостановлении Task Нет (Task.CompletedTask синглтон) Да Task<bool> Нет (Task.FromResult возвращает один из двух кэшированных экземпляров true/false) Да Task<T> Да* Да ValueTask и ValueTask<T> Нет Да**

*) Есть и другие исключения, такие как int в диапазоне [-1, 9), null, default(T) для некоторых типов значений.

**) Если не используется IValueTaskSource, который не распространен в коде приложения.

Несколько лет назад Стивен Тауб написал сообщение в блоге , в котором подробно раскрывается эта тема и служит прекрасным обзором. Я также могу порекомендовать блог Стивена Клири, где он объясняет темы, связанные с async/await и многое другое.

Но в целом используйте то, что вы предложили изначально, и все будет в порядке.

private async Task<bool> IsUserSetAsync()
{
    return user != null || await getUserAsync();
}

или, если вам действительно необходимо исключить ожидание:

private Task<bool> IsUserSetAsync()
{
    if (user != null)
    {
        return Task.FromResult(true);
    }
    else
    {
        return getUserAsync();
    }
}

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

Greg L. 25.07.2024 15:18

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