Интерфейсы и асинхронные практики

#c# #async-await #interface

#c# #асинхронное ожидание #интерфейс

Вопрос:

У меня есть интерфейс

 public interface IUserFactory
{
       User GetUser(string id);
       void DeleteUser(string id);
}
 

Текущий класс, который реализует, использует синхронные функции.

Теперь у меня будет реализация, которая использует HttpClient, вызовы которого являются асинхронными.

Сейчас я делаю так, чтобы новый класс UserFactoryWeb реализовал два набора функций:

 Task<User> GetUserAsync(string id)
Task DeleteUserAsync(string id)
 

и

 User GetUser(string id)
void DeleteUser(string id)
 

методы интерфейса. В методах интерфейса я использую трюк:

 var t = Task.Run(async code...)
t.Wait();
 

для предотвращения взаимоблокировок.

Есть ли другие варианты?

Кроме того, как вы можете поддерживать асинхронные вызовы в интерфейсах? Кажется сумасшедшим, что асинхронность не является частью подписи интерфейса.

Комментарии:

1. Что вы ожидаете async от определения интерфейса? Возврат Task<T> (как и любой интерфейс, который имеет возможность асинхронных реализаций) означает «результат может быть или не быть доступен, дождитесь завершения этой задачи, чтобы получить его»… но что именно async следует добавить к этому?

2. Как правило, вы не должны запускать асинхронный код в методах синхронизации. Task.Run — это способ сделать это, но это может привести к взаимоблокировкам. Если вы используете в asp.net ядро вы могли бы создать промежуточное программное обеспечение, которое загружает пользователя в контекст. Я предполагаю, что вы в любом случае должны кэшировать информацию о пользователе, поэтому такая синхронизация имеет смысл, но, вероятно, это действительно старый интерфейс.

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

4. t.Wait(); блокирует ли вызов

5. @FilipCordas — Я думал, что выполнение асинхронной задачи через Task.Run — это способ избежать взаимоблокировок. Проблема в том, что мне пришлось бы изменить интерфейс на: Task<User> getUser Задача deleteUser, а затем обновить все реализации, включая синхронные, для его поддержки.

Ответ №1:

Давайте объясним, почему интерфейсам не нужно async ключевое слово

  1. Метод, возвращающий Task / ValueTask , не обязательно должен быть асинхронным методом (метод с async ключевым словом).
  2. Асинхронный метод — это, по сути, компилятор, помогающий
    1. Возврат Task / ValueTask (если не недействителен),
    2. Размещение исключений для возвращаемого Task / ValueTask (если не недействительно)
    3. Разрешение использования await ключевого слова
  3. Асинхронный метод выполняется синхронно до тех пор, пока не достигнет первого await выражения, после чего метод приостанавливается до завершения ожидаемой задачи.
  4. Метод Async не нуждается в an await , и в этом случае это просто компилятор для возвращаемого значения и исключения, если оно не является недействительным (см. Пункт 1.1 и 1.2 соответственно)
  5. Вы можете использовать await все, что поддерживает INotifyCompletion , а возвращаемый тип предоставляет 3 элемента
    • Завершено
    • Завершено
    • Получить результат
  6. Task и ValueTask могут ожидаться независимо от того, были ли они возвращены асинхронным методом (см. Пункт 5)
  7. Интерфейсы — это просто контракты между разработчиком и вызывающей стороной и не дают никаких указаний на то, как реализуется контракт, они просто содержат определения для группы связанных функций
  8. Определение таких членов не требует гарантии того, был ли метод подключен к async ключевому слову an (или каким-либо другим странным вещам, которые вы делаете в этом методе), только то, что он возвращает Task / ValueTask

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

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

Такой метод, как

 public static async Task Test1()
 

Фактически компилируется для

 [AsyncStateMachine(typeof(<Test1>d__0))]
public static Task Test1()
 

Дополнительные ресурсы

Комментарии:

1. Что происходит, когда он возвращает void?

2. @bpeikes он по-прежнему не нуждается async в определении интерфейса, интерфейсы — это просто контракты, а не детали реализации

Ответ №2:

Вы можете обернуть свой возвращаемый тип в Task следующим образом:

 public interface IUserFactory
{
       Task<User> GetUser(string id);
       Task DeleteUser(string id);
}
 

Комментарии:

1. Похоже, это именно то, что уже предлагал ОП… Насколько я понял вопрос, они обеспокоены отсутствием async самого определения интерфейса … (но именно так я прочитал вопрос)

Ответ №3:

Добавление async к сигнатуре метода вообще не изменяет контракт метода. В async IL вообще нет эквивалента. Реализация async языковой функции полностью находится в компиляторе C #.

Это довольно просто продемонстрировать на простом примере и с помощью декомпилятора IL.

 public class C {
    public async Task Example1(Task t) => await t;
    public Task Example2(Task t) => t;
}
 
 // snip
public Task Example1(Task t)
// snip
public Task Example2(Task t)
// snip