繁体   English   中英

取消代币注入

[英]Cancellation Token Injection

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

我们有一个 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 流等。我们拥有您期望的所有常用存储库模式和消息传递实践。 我们尽量保持我们的方法简洁,并有一个单一的责任。 我的技术负责人显然被“福勒”这个词激怒了。 所以我们的类大小和函数体很小,但我们的调用链非常非常深。

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

    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 列表和每个方法 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 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 个注入作用域:Singleton、Scoped 和 Transient。 其中两个排除使用共享令牌。

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

使用AddTransient添加的 DI 服务可能会按需实例化,并且您可能会遇到为已取消的令牌创建新实例的问题。 他们可能需要某种方式将当前令牌传递给[FromServices]或其他一些库更改。

但是,对于AddScoped我认为有一种方法,我对类似问题这个答案帮助了- 您不能将令牌本身传递给 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))

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM