Внедрение токена отмены

Sep 30 2020

Я хотел бы иметь возможность каждый раз передавать токены отмены через внедрение зависимостей, а не в качестве параметров. Это вещь?

У нас есть приложение 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. Так что, может быть, ламы это

Ответы

1 Keith Oct 06 2020 at 04:19

Прежде всего, есть 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изнутри действия контроллера или добавляли его к параметрам действия.