Это НЕ провал чего-либо. Я просто пытаюсь определить, как писать на С# более элегантно.
Я обнаружил, что в проекте 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).Task.FromResult<>
(я не
знаю, где я подхватил эту привычку) это добавляет ненужные накладные расходы (фузи).Почему бы просто не всегда звонить await classX.getUserAsync()
? Разве не достаточно умно сначала проверить classX.user
?
getUserAsync()
имя должно измениться на setUserAsync()
, а условие user is null должно находиться под setUserAsync()
. так что вам остаётся только проверить if (classX.user is not null)
phuzi - спасибо, удалил мое плохое кодирование с помощью classX.user Heretic Monkey - Вы правы, но меня все еще интересует вопрос производительности, сильно ли снижается производительность при вызове этой асинхронной функции Task<bool> и ее использовании проверить наличие значения null или лучше проверить наличие значения null перед вызовом getUserAsync и созданием связанной с ним задачи?
@GregL. производительность будет одинаковой независимо от того, проверяете ли вы спереди или внутри getUserAsync()
Спасибо, Акаш... Я думал, что из-за таких тем, как reddit.com/r/csharp/comments/18gjudv/… Мне следует избегать ненужных вызовов асинхронных функций, проверяя значение null перед вызовом... это не так тогда большое дело?
Какой код обычно входит в ветку else этого if? Это какая-то распространенная обработка ошибок? Что произойдет, если у меня нет пользователя и я не смогу решить эту проблему?
Это не ошибка, но этот код рассматривает пользователя как анонимного (неаутентифицированного/неизвестного) пользователя.
ValueTask
позволит избежать попадания в перформанс, если user
уже получен.
Charlieface: Хорошо, я не знал о ValueTask, я начинаю читать о нем, похоже, мне следует включить нулевую проверку в SetUserAsync() и использовать ValueTask.
Если «пользователь не равен нулю», почему вам все еще нужен асинхронный вызов для получения «находящегося там пользователя»? Это почти подразумевает состояние гонки: пользователь может исчезнуть.
Я думаю, ты был почти там с
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?
@GregL. Я добавил немного await Task.FromResult()
к своему ответу.
Спасибо, Фузи, я возвращаюсь к коду и удаляю некоторые Task.FromResults. Я пытался проверить ваш комментарий, но у меня нет такой репутации, чтобы сделать это.
Не стесняйтесь также проголосовать за ответ 😉
Проголосовал за! Спасибо!
Я бы дал читабельный ответ - в конце концов, код, написанный так, чтобы его было легко понять, является самым элегантным из всех :)
private async Task<bool> IsUserSet()
{
if (user == null)
{
return false;
}
else
{
return await getUserAsync();
}
}
Говоря об этом, я бы, вероятно, прокомментировал выбор имени для getUserAsync(), поскольку на самом деле он не возвращает пользователя. Правильный выбор имени сделал бы код более читабельным.
Педро: Да, я вижу, что вы правы, как и Акаш прокомментировал выше. Я меняю его на setUserAsync. Спасибо за пример, это звучит правильно, хотя мне все еще интересно вставить еще одну асинхронную задачу, поскольку, видимо, ReSharper это не очень нравится.
В каком-то смысле я бы посчитал асинхронную задачу не очень хорошим дизайном - я думаю, она нарушает SRP, потому что метод, отвечающий за проверку того, установлен ли пользователь, также пытается его создать.
Перейдем к сути вопроса:
Тем не менее, мне интересно, стоит ли снижение производительности (есть один с вставкой другой задачи, не так ли?) или есть способ сделать это без вставки другой задачи. (Или, может быть, мне просто нужно немного узнать, как писать более чистый код 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();
}
}
Ух ты, я только что проверил ответ Фузи как ответ, а затем отредактировал свое исходное сообщение, чтобы суммировать другие комментарии, а затем, сохранив все это, увидел, что ваш ответ был только что опубликован. Спасибо, ваша информация действительно информативна и помогает мне лучше понять ситуацию, а также включает дополнительные ссылки. Я очень благодарен всем за информацию об асинхронной обработке задач.
if (classX.user ?? await classX.getUserAsync())
не сработает, потому чтоclassX.user
должен быть типаbool?
, и я бы поставил деньги на то, чтобы он был другого типа.