#c# #asp.net-core #async-await
#c# #asp.net-core #асинхронный-ожидание
Вопрос:
В моем ASP.NET Основное приложение, в некоторые моменты я запрашиваю данные у пары объявлений. Поскольку это AD, выполнение запросов занимает некоторое время, а API DirectoryServices содержит только синхронные вызовы.
Рекомендуется ли пытаться переносить вызовы AD sync как асинхронные? Я думаю, что это делается так (просто пример, а не реальный запрос):
private async Task<string[]> GetUserGroupsAsync(string samAccountName)
{
var func = new Func<string, string[]>(sam =>
{
var result = new List<string>();
using (var ctx = new PrincipalContext(ContextType.Domain, "", "", ""))
{
var p = new UserPrincipal(ctx)
{
SamAccountName = sam
};
using (var search_obj = new PrincipalSearcher(p))
{
var query_result = search_obj.FindOne();
if (query_result != null)
{
var usuario = query_result as UserPrincipal;
var directory_entry = usuario.GetUnderlyingObject() as DirectoryEntry;
var grupos = usuario.GetGroups(ctx).OfType<GroupPrincipal>().ToArray();
if (grupos != null)
{
foreach (GroupPrincipal g in grupos)
{
result.Add(g.Name);
}
}
}
}
}
return result.ToArray();
});
var result = await Task.Run(() => func(samAccountName));
return resu<
}
Комментарии:
1. Обертывание его,
Task
чтобы сделать его «асинхронным», не делает ничего, кроме добавления дополнительного уровня накладных расходов. Не делайте этого. В этом нет ни единого преимущества.2. Нет, это не так. Основная идея
async/await
— совместное использование потоков: пока вы выполняете работу, которая не требует запуска потока, кто-то другой использует ее. В вашем примере вы делитесь своим исходным потоком, но используете другой, чтобы дождаться завершения операции синхронизации.3. В общем, если у вас есть HTTP-вызовы, лучше создавать асинхронные методы, а асинхронность немного заразна, поэтому вам нужно сделать всю цепочку асинхронной..
4. Пока вы используете асинхронный шаблон, ваша функция полностью синхронизирована: я имею в виду, что запущенный процесс
GetUserGroupsAsync
ожидает завершения задачи (в режиме онлайнvar result = await ...
). Так что или ваш пример слишком прост, или вообще этот подход является ложным асинхронным.5. Обязательное чтение: должен ли я предоставлять асинхронные оболочки для синхронных методов?
Ответ №1:
Это хорошая практика
Обычно нет.
В настольном приложении, где вы не хотите задерживать поток пользовательского интерфейса, эта идея действительно может быть хорошей идеей. Это Task.Run
переносит работу в другой поток, и поток пользовательского интерфейса может продолжать отвечать на вводимые пользователем данные, пока вы ждете ответа.
Вы отметили ASP.NET . Ответ там также «это зависит». ASP.NET имеет ограниченное количество рабочих потоков, которые ему разрешено использовать. Преимущество асинхронного кода заключается в том, что он позволяет потоку работать с каким-либо другим запросом, пока вы ждете ответа. Таким образом, вы можете обслуживать больше запросов с тем же количеством доступных потоков. Это помогает повысить общую производительность вашего приложения.
Если вы вызываете await GetUserGroupsAsync()
, то нет абсолютно никакой пользы в том, что вы делаете. Вы освобождаете вызывающий поток, но вы создали новый поток, который будет заблокирован до тех пор, пока не будет возвращен ответ. Таким образом, ваша чистая экономия потоков равна нулю, и у вас есть дополнительные затраты процессора на настройку задачи.
Если вы собираетесь вызывать GetUserGroupsAsync()
, а затем выходить и получать другие данные, пока вы ждете ответа, то это может сэкономить время. Это не сэкономит потоки, а только время. Но вы должны осознавать, что теперь вы используете два потока для каждого запроса вместо одного, что означает, что вы можете нажать ASP.NET максимальное количество потоков увеличивается быстрее, что потенциально снижает общую производительность вашего приложения.
Но хотите ли вы сэкономить время в ASP.NET , или если вы хотите освободить поток пользовательского интерфейса в настольном приложении, я бы все равно сказал, что вам не следует использовать Task.Run
inside GetUserGroupsAsync()
. Если вызывающий абонент хочет передать это ожидание другому потоку, чтобы затем он мог получить другие данные, тогда вызывающий абонент может использовать Task.Run
, например:
var groupsTask = Task.Run(() => GetUserGroupsAsync());
// make HTTP request or get some other external data while we wait
var groups = await groupsTask;
Ответ №2:
Решение о том, следует ли вам создавать метод для класса, должно зависеть от ответа на вопрос: если кто-то подумает о том, что представляет этот класс, подумает ли он, что этот класс будет обладать такой функциональностью?
Сравните это с классом string и методами о равенстве строк. Большинство людей подумали бы, что две строки равны, если они содержат точно такие же символы в одном и том же порядке. Однако для многих приложений может быть удобно иметь возможность сравнивать две строки без учета регистра. Вместо изменения метода равенства string создается новый класс. Этот класс StringComparer содержит множество методов для сравнения строк с использованием разных определений равенства.
Если бы кто-то сказал: «Хорошо, я только что создал класс, который представляет несколько методов для сравнения двух строк на равенство». Ожидаете ли вы, что сравнение с нечувствительностью к регистру является одним из методов этого класса? Конечно, вы бы это сделали!
То же самое должно быть и с вашим классом. Я не знаю, что представляет ваш класс. Однако, по-видимому, вы подумали, что кто-то, у кого есть объект этого класса, был бы рад «получить группы пользователей». Он счастлив, что ему не нужно знать, как кто-то создал этот метод для него, и что ему не нужно знать внутренности класса, чтобы иметь возможность получать группы пользователей.
Это скрытие информации является важной особенностью классов. Это дает создателю класса свободу внутреннего изменения того, как работает класс, без необходимости изменять использование класса.
Итак, если все, кто знает, что представляет ваш класс, подумают: «конечно, получение групп пользователей займет значительное количество времени» и «конечно, мой поток будет бездействовать при получении групп пользователей», тогда пользователи вашего класса будут ожидать наличия asyn-await, чтобы предотвратитьпраздное ожидание.
С другой стороны, может случиться так, что пользователи вашего класса скажут: «Ну, я знаю, что для получения групп пользователей потребуются некоторые сложные вычисления. Это займет некоторое время, но моя тема будет очень занята «. В этом случае они не будут ожидать асинхронного метода.
Предполагая, что у вас есть неасинхронный метод для получения групп пользователей:
string[] GetUserGroups(string samAccountName) {...}
Асинхронный метод был бы очень простым:
Task<string[] GetUserGroupsAsync(string samAccountName)
{
return Task.Run(() => GetUserGroups(samAccountName));
}
Единственное, что вам нужно было бы решить, это: ожидают ли пользователи моего класса этого метода?
Преимущества и недостатки
Недостаток наличия метода синхронизации и асинхронного метода:
- Люди, которые узнают о вашем классе, должны узнать о большем количестве методов
- Пользователи вашего класса не могут решить, как асинхронный метод вызывает метод синхронизации, без создания дополнительного асинхронного метода, что только добавит путаницы
- Вам придется добавить дополнительный модульный тест
- Вам придется поддерживать асинхронный метод вечно.
Преимущества использования асинхронного метода:
- Если в будущем группа пользователей будет извлечена из другого процесса, например, из базы данных или XML-файла, или, возможно, из Интернета, тогда вы можете внутренне изменить класс, не меняя многих, многих пользователей (в конце концов, все ваши классы очень популярны, не так ли:)
Заключение
Если люди смотрят на ваш класс, и они даже не подумают, что выборка групп пользователей будет асинхронным методом, тогда не создавайте его.
Если вы думаете, что, возможно, в будущем может случиться так, что другой процесс предоставит группы пользователей, тогда было бы разумно подготовить своих пользователей к этому.