Framework Design Principle
Frameworks must be designed starting from a set of usage scenarios and code samples implementing these scenarios.
Если непонятно, про что речь — тынц на подробное объяснение.
На конкретном примере:
// 1. У нас есть действие, которое что-то возвращаетvar result = await GetResultAsync(someParams);
// 2. Мы хотим запустить это действие в каком-то контекстеusing(var contex = GetCaptureContext(someParams))
{
var result = await GetResultAsync(someParams);
}
// 3. Мы хотим сами управлять вызовами внутри контекста, к примеру, запускать код несколько раз.
// Оборачиваем действие в делегат, получаемvar result = await retryPolicy.ExecuteAndCaptureAsync(() => GetResultAsync(someParams)));
// 4. Мы хотим кроме самого результата вернуть что-то ещё, к примеру, количество итерацийvar executeResult = await retryPolicy.ExecuteAndCaptureAsync(() => GetResultAsync(someParams)));
var result = executeResult.Result;
Смысл такой: каждый шажок позволяет решить _реальную_ проблему и сам по себе не создаёт новых. С мелкими шажками это отследить очень легко и не требуется прыгать назад чтобы уточнять, не забыл ли чего. Слона едят по кусочкам.
Если на этот принцип забить и замахнуться на слона целиком, то получается сегодняшний пример: код умеет всё, кроме изначального сценария
Что самое обидное, вот в этот момент люди _никогда_ не пробуют остановиться и поискать момент, в который всё пошло не так. Вместо этого проблему маскируют новыми костылями и так до тех пор, пока объём костылей не перерастёт на порядок объём собственно полезного кода.
S>Здравствуйте, Artem Korneev, Вы писали:
AK>>Хочется всё это переписать к более понятному виду.
S>Использовать await в каждом методе по цепочке вызовов абсолютно необязательно.
S>
Здравствуйте, ifle, Вы писали:
I>В вашем коде есть один косяк не имеющий отношения к async. httpclient должен быть статическим
Статическим ему конечно же быть вредно, а вот переиспользовать инстанс в рамках клиента — может быть и нужно, если хочется получить пул соединений в рамках группы соединений (если грубо, на каждый инстанс HttpClient — своя группа соединений).
PS: А ещё лучше выбросить к черту весь этот недосетевой стэк. Но это уже скорее из области вредных советов в рамках данного форума.
Написал я тут в коде одну конструкцию, которая пугает меня своей сложностью.
Это — кусок кода класса RestServiceClient, предназначенного для отправки запросов HTTP-серверам. Эта функция создаёт HttpClient и вызывает коллбэк, указанный в качестве параметра.
public async Task InvokeHttpCall(Func<HttpClient, Task<HttpResponseMessage>> callback)
{
using (var httpClient = new HttpClient())
{
HttpResponseMessage response = null;
var http = httpClient;
await this.retryPolicy.ExecuteAndCaptureAsync(
async () =>
await Task.Run(
async () => response = await callback.Invoke(http)));
}
}
Сделано это для того чтоб не повторять один и тот же код для обёртки каждого типа HTTP-запроса. Здесь я привожу упрощённый код, реально там ещё установка разных заголовков аутентификации, замеры времени обработки запроса и т.д. Результат выполнения запроса (переменная response) используется потом для возврата HttpStatusCode. Там несколько похожих методов, с десериализацией результата и без него, я привожу здесь простейший вариант. Используется этот метод так:
Здесь идёт обёртка для DELETE-запоса, остальные типы запросов реализованы аналогично, но с дополнительными параметрами.
Вот эта строчка:
await this.retryPolicy.ExecuteAndCaptureAsync
Использует библиотеку Polly для применения разных политик обработки ошибок соединения (retry, circuit breaker, и т.д.). Оно тоже всё асинхронное и принимает коллбэки в качестве параметров.
Так вот.. это всё работает, но результат мне самому кажется излишне сложным. Особенно меня смущает вот эта лестница из async/await:
Хочется всё это переписать к более понятному виду, потому как сейчас, кроме меня, этот код врятли кто-нибудь разберёт в моей команде. Коллеги, подскажите, как и с какой стороны к этому лучше подходить? Нет ли тут каких-то явных косяков, которые я не замечаю? Мне кажется, что что-то в этой последовательности async/await можно упростить, но я пока не смог.
Здравствуйте, ifle, Вы писали:
I>В вашем коде есть один косяк не имеющий отношения к async. httpclient должен быть статическим
Не подходит по условиям использования.
Этот RestClient используется в multi-tenant облачном сервисе, т.е. там два последовательных запроса могут придти от разных пользователей разных организаций. В этом HttpClient есть поле DefaultRequestHeaders, где выставляются хэдеры авторизации. Т.е. сделав статическим, мы сразу ломаем всю аутентификацию.
Используется всё это сравнительно редко (по крайней мере, пока), так что проблем из-за пересоздания объекта HttpClient нет. Но за информацию спасибо.
Здравствуйте, Artem Korneev, Вы писали:
AK>Не подходит по условиям использования.
Тут важнее — нужен пулинг соединений или нет? А то при достаточно небольшой нагрузке можно все tcp порты скушать (предполагая что удаленный хост один, и есть че пулить).
AK>В этом HttpClient есть поле DefaultRequestHeaders, где выставляются хэдеры авторизации. Т.е. сделав статическим, мы сразу ломаем всю аутентификацию.
Можно спокойно передавать все необходимые хедеры собственно в момент запроса (при подготовке HttpRequestMessage).
AK>Используется всё это сравнительно редко (по крайней мере, пока), так что проблем из-за пересоздания объекта HttpClient нет.
А, ну тогда наверное ок.
Еси в конце концов всё равно у нас всё сводится к синхронному коду, то на кой стартавать таск вообще, можно прямо в ExecuteAndCaptureAsync делегат передать который синхронно будет выполняться
Здравствуйте, Tom, Вы писали:
Tom>Еси в конце концов всё равно у нас всё сводится к синхронному коду, то на кой стартавать таск вообще, можно прямо в ExecuteAndCaptureAsync делегат передать который синхронно будет выполняться
callback запускает новую задачу, её надо дождаться и запихнуть результат в response. А Task.Run тут вообще лишний.
Здравствуйте, TK, Вы писали:
TK>А кто вас заставляет использовать эти все async / await?
Без любого из этих async / await оно тупо не работало.
Я с той работы ушёл уже, не могу сейчас тот код пощупать. На днях попробую разобраться с .net core на своём домашнем Linux-десктопе, попробую ещё разок.
Вообще, у меня в голове крутится мысль, что лишние там не async / await, вернее, не только async / await, но и асинхронность тех методов в целом. Мне думается, что достаточно там вызывать ExecuteAndCaptureAsync, оно уже должно обеспечивать асинхронность.
Но надо попробовать. Как попробую — напишу.