取消令牌注入

use*_*608 19 .net c# cancellation-token asp.net-core

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

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

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

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

现在,我想成为一名优秀的异步程序员,并确保我的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);
}
Run Code Online (Sandbox Code Playgroud)

根据我们领域复杂性的需要,这种情况会无限地持续下去。它在我们的代码库中出现的次数似乎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);
}
Run Code Online (Sandbox Code Playgroud)

然后,这将通过 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);
}

Run Code Online (Sandbox Code Playgroud)

我疯了吗?我是否每次都会传递该死的令牌,并赞扬异步诸神所给予的赏金?我应该重新训练成为一名美洲驼农民吗?他们看起来不错。甚至问这个也是某种异端邪说吗?我现在该悔改吗?我认为要使 async/await 正常工作,令牌必须位于 func decl 中。所以,也许是美洲驼

Hak*_*tık 7

这是一个比我对这个问题的原始答案更简单的答案,复制自下面亚伦·奎南的答案

services.AddHttpContextAccessor();
services.AddScoped(typeof(CancellationToken), serviceProvider =>
{
   IHttpContextAccessor httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>();
   return httpContext.HttpContext?.RequestAborted ?? CancellationToken.None;
});
Run Code Online (Sandbox Code Playgroud)

然后就可以CancellationToken直接注入并使用了



原始答案
我认为你的想法很好,我认为你不需要后悔或悔改。
这是个好主意,我也考虑过,并且实现了自己的解决方案

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;
}
Run Code Online (Sandbox Code Playgroud)

注册应该是这样的

services.AddHttpContextAccessor();
services.AddScoped<RequestCancellationBase, RequestCancellation>();
Run Code Online (Sandbox Code Playgroud)

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

这是如何使用它的示例

public sealed class Service1
{
    private readonly RequestCancellationBase _cancellationToken;
    
    public Service1(RequestCancellationBase cancellationToken)
    {
         _cancellationToken = cancellationToken;
    }

    public async Task SomeMethod()
    {
        HttpClient client = new();
        // passing RequestCancellationBase object instead of passing CancellationToken
        // without any casting
        await client.GetAsync("url", _cancellationToken);
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Syl*_*gue 4

这就是我将 HttpContext 取消令牌注入到我的存储库中的方式。

WebApi/Program.cs

builder.Services.AddScoped<ICancellationTokenService, CurrentCancellationTokenService>();
Run Code Online (Sandbox Code Playgroud)

WebApi/服务/CurrentCancellationTokenService.cs

public class CurrentCancellationTokenService : ICancellationTokenService
{
    public CancellationToken CancellationToken { get; }

    public CurrentCancellationTokenService(IHttpContextAccessor httpContextAccessor)
    {
        CancellationToken = httpContextAccessor.HttpContext!.RequestAborted;
    }
}
Run Code Online (Sandbox Code Playgroud)

核心/域/接口/ICancellationTokenService

public class ICancellationTokenService
{
    public CancellationToken CancellationToken { get; }
}
Run Code Online (Sandbox Code Playgroud)

持久化/Dal/Repositories/ClientRepository.cs

public class ClientRepository : IClientRepository
{
    private readonly CancellationToken _cancellationToken;

    public ClientRepository(ICancellationTokenService cancellationTokenService, ...)
    {
        _cancellationToken = cancellationTokenService.CancellationToken;
    }

    public Task<Client?> GetByIdAsync(int clientId)
    {
        return _context.Client.FirstOrDefaultAsync(x => x.Id == clientId, _cancellationToken);
    }
}
Run Code Online (Sandbox Code Playgroud)