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 中。所以,也许是美洲驼
这是一个比我对这个问题的原始答案更简单的答案,复制自下面亚伦·奎南的答案。
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)
这个解决方案对我有帮助,希望对你也有帮助
这就是我将 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)
归档时间: |
|
查看次数: |
7173 次 |
最近记录: |