取消令牌注入

问题描述

我希望能够通过依赖项注入传递取消令牌,而不是每次都作为参数传递。这是东西吗?

我们有一个asp.net-core 2.1应用程序,在这里我们将来自控制器的调用传递到迷宫般的异步库,处理程序和其他服务中,以满足我们服务的金融科技监管领域的拜占庭式需求。

在请求的顶部,我可以声明我想要取消令牌,然后我将得到一个

    [HttpPost]
    public async Task<IActionResult> DoSomeComplexThingAsync(object thing,CancellationToken cancellationToken) {
        await _someComplexLibrary.DoThisComplexThingAsync(thing,cancellationToken);
        return Ok();
    }

现在,我想成为一名优秀的异步程序员,并确保将我的cancellationToken传递给整个调用链中的每个异步方法。我想确保将其传递给EF,System.IO流等。我们具有您期望的所有常用存储库模式和消息传递实践。我们试图使我们的方法简明扼要,并负有单一责任。 “ Fowler”一词显然引起了我的技术主管。因此,我们的类大小和函数体很小,但是我们的调用链非常非常深。

这意味着每个层,每个功能都必须交出该死的令牌:

    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在我们的代码库中的出现次数似乎比任何其他术语都多。即使我们声明了一百万个对象类型,我们的arg列表通常也已经太长(即不止一个)。现在,在每个arg列表,每个方法的说明中,我们都有这个多余的取消令牌伙伴。

我的问题是,由于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 decl中。所以,也许是美洲驼

解决方法

我认为你的想法很好,我认为你不需要后悔或忏悔,这是个好主意,我也考虑过,我得出了解决方案

public abstract class RequestCancellationBase
{
    public abstract CancellationToken Token { get; }

    public static implicit operator CancellationToken(RequestCancellationBase requestCancellation) =>
        requestCancellation.Token;
}


public class RequestCancellation : RequestCancellationBase
{
    private readonly IHttpContextAccessor _context;

    public RequestCancellation(IHttpContextAccessor context)
    {
        _context = context;
    }

    public override CancellationToken Token => _context.HttpContext.RequestAborted;
}

注册应该是这样的

services.AddSingleton<IHttpContextAccessor,HttpContextAccessor>();
services.AddScoped<RequestCancellationBase,RequestCancellation>();

现在你可以在任何你想要的地方注入 RequestCancellationBase
更好的是你可以直接将它传递给每个需要 CancellationToken 的方法,这是因为 public static implicit operator CancellationToken(RequestCancellationBase requestCancellation)

这个解决方案对我有帮助,希望对你也有帮助

,

首先,有3种注入范围:单例,有范围和瞬态。其中两个排除使用共享令牌。

添加有AddSingleton的DI服务存在于所有请求中,因此任何取消令牌都必须传递给特定方法(或整个应用程序)。

添加有AddTransient

DI服务可能会按需实例化,并且可能会出现以下问题:为已取消的令牌创建新实例。他们可能需要某种方式将当前令牌传递到[FromServices]或进行其他一些库更改。

但是,对于AddScoped,我认为是有办法的,this answer to my similar question为我提供了帮助-您无法将令牌本身传递给DI,但可以传递IHttpContextAccessor

因此,在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))

每个HTTP请求将检索一次IHttpContextAccessor服务,并且?.HttpContext?.RequestAborted将返回相同的CancellationToken,就像您从控制器操作或将其添加到操作的参数中。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...