我想通过依赖注入而不是每次作为参数传递取消标记。这可行吗?
我们有一个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
在我们的代码库中出现的次数比任何其他术语都要多。尽管我们声明了数百万个对象类型,但我们的参数列表通常已经太长了(即超过一个)。现在我们还有这个额外的小取消令牌伙伴在每个参数列表和每个方法声明中挂着。
我的问题是,既然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正常工作,令牌必须在函数声明中。所以,也许就是骆驼。
CancellationToken
的重复是我在编写可取消代码时也考虑过很多次的事情。据我所知,它源自于.NET 4,当时取消概念被添加到BCL中,远在DI成为问题之前。您可以通过提交问题或讨论,在https://github.com/dotnet/runtime上获得更好的响应。此外,羊驼会对您的异端行为进行唾弃,因此养殖可能不可取。 - Ian KempSomeMessageHandler.Handle
是*不可取消的。在完成所有工作后返回结果之前检查令牌并不是取消的意义所在。取消是指实际停止工作而不仅仅是中断调用流程。 - pokeIHttpAccessor
将CancellationToken
注入到服务中。 - Métoule