Внедрение токена отмены
Я хотел бы иметь возможность каждый раз передавать токены отмены через внедрение зависимостей, а не в качестве параметров. Это вещь?
У нас есть приложение asp.net-core 2.1, в котором мы передаем вызовы от контроллеров в лабиринт асинхронных библиотек, обработчиков и других служб, чтобы удовлетворить византийские потребности регулирующего домена финтех, который мы обслуживаем.
В верхней части запроса я могу объявить, что мне нужен токен отмены, и я его получу:
[HttpPost]
public async Task<IActionResult> DoSomeComplexThingAsync(object thing, CancellationToken cancellationToken) {
await _someComplexLibrary.DoThisComplexThingAsync(thing, cancellationToken);
return Ok();
}
Теперь я хочу быть хорошим асинхронным программистом и убедиться, что my cancellationTokenпередается каждому асинхронному методу вниз по цепочке вызовов. Я хочу убедиться, что он передается в потоки EF, System.IO и т. Д. У нас есть все обычные шаблоны репозитория и методы передачи сообщений, которые вы ожидаете. Мы стараемся, чтобы наши методы были краткими и несем единственную ответственность. Моего технического руководителя заметно возбуждает слово «Фаулер». Итак, наши размеры классов и тела функций небольшие, но наши цепочки вызовов очень и очень глубокие.
Это означает, что каждый слой, каждая функция должны передать чертов токен:
private readonly ISomething _something;
private readonly IRepository<WeirdType> _repository;
public SomeMessageHandler(ISomething<SomethingElse> something, IRepository<WeirdType> repository) {
_something = something;
_repository = repository;
}
public async Task<SomethingResult> Handle(ComplexThing request, CancellationToken cancellationToken) {
var result = await DoMyPart(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
result.SomethingResult = await _something.DoSomethingElse(result, cancellationToken);
return result;
}
public async Task<SomethingResult> DoMyPart(ComplexSubThing request, CancellationToken cancellationToken) {
return await _repository.SomeEntityFrameworkThingEventually(request, cancellationToken);
}
Это продолжается до бесконечности, в соответствии с потребностями сложности нашей предметной области. Похоже, что CancellationTokenв нашей кодовой базе встречается чаще, чем любой другой термин. Наши списки аргументов часто и так уже слишком длинные (то есть их больше одного), даже если мы объявляем миллион типов объектов. И теперь у нас есть этот дополнительный маленький приятель по токенам отмены, который слоняется в каждом списке аргументов, в каждом методе decl.
У меня вопрос, поскольку Kestrel и / или конвейер в первую очередь дали мне токен, было бы здорово, если бы у меня было что-то вроде этого:
private readonly ISomething _something;
private readonly IRepository<WeirdType> _repository;
private readonly ICancellationToken _cancellationToken;
public SomeMessageHandler(ISomething<SomethingElse> something, ICancellationToken cancellationToken) {
_something = something;
_repository = repository;
_cancellationToken = cancellationToken;
}
public async Task<SomethingResult> Handle(ComplexThing request) {
var result = await DoMyPart(request);
_cancellationToken.ThrowIfCancellationRequested();
result.SomethingResult = await _something.DoSomethingElse(result);
return result;
}
public async Task<SomethingResult> DoMyPart(ComplexSubThing request) {
return await _repository.SomeEntityFrameworkThingEventually(request);
}
Затем это будет передаваться через композицию DI, и когда у меня будет что-то, для чего явно нужен токен, я могу сделать это:
private readonly IDatabaseContext _context;
private readonly ICancellationToken _cancellationToken;
public IDatabaseRepository(IDatabaseContext context, ICancellationToken cancellationToken) {
_context = context;
_cancellationToken = cancellationToken;
}
public async Task<SomethingResult> DoDatabaseThing() {
return await _context.EntityFrameworkThing(_cancellationToken);
}
Я чокнутый? Неужели я просто каждый раз передаю проклятый жетон и восхваляю асинхронных богов за назначенную награду? Стоит ли мне просто переучиться на ламфермера? Они кажутся хорошими. Это вообще ересь какая-то? Должен ли я сейчас каяться? Я думаю, что для правильной работы async / await токен должен быть в объявлении func. Так что, может быть, ламы это
Ответы
Прежде всего, есть 3 области применения: Singleton, Scoped и Transient. Двое из них исключают использование общего токена.
Сервисы DI, добавленные с помощью, AddSingletonсуществуют во всех запросах, поэтому любой токен отмены должен быть передан конкретному методу (или всему вашему приложению).
Службы DI, добавленные с помощью, AddTransientмогут быть созданы по запросу, и вы можете столкнуться с проблемами, когда новый экземпляр создается для токена, который уже отменен. Возможно, им понадобится какой-то способ для передачи текущего токена [FromServices]или какое-то другое изменение библиотеки.
Однако, поскольку AddScopedя думаю, что есть способ, и мне помог этот ответ на мой аналогичный вопрос - вы не можете передать сам токен DI, но вы можете пройти IHttpContextAccessor.
Итак, in Startup.ConfigureServicesили метод расширения, который вы используете для регистрации любого IRepositoryиспользования:
// For imaginary repository that looks something like
class RepositoryImplementation : IRepository {
public RepositoryImplementation(string connection, CancellationToken cancellationToken) { }
}
// Add a scoped service that references IHttpContextAccessor on create
services.AddScoped<IRepository>(provider =>
new RepositoryImplementation(
"Repository connection string/options",
provider.GetService<IHttpContextAccessor>()?.HttpContext?.RequestAborted ?? default))
Эта IHttpContextAccessorслужба будет извлечена один раз для каждого HTTP-запроса, и ?.HttpContext?.RequestAbortedона вернет то же самое, CancellationTokenкак если бы вы вызывали this.HttpContext.RequestAbortedизнутри действия контроллера или добавляли его к параметрам действия.