繁体   English   中英

我是否应该通过 asp.net 核心 3.1 web ZDB974238D704A 中的所有 class 方法检测取消令牌?

[英]Should I plumb Cancellation Tokens through all of my class methods in an asp.net core 3.1 web API?

我已经在 asp.net 核心 3.1 中构建了一个 web 服务器一段时间,并且我开始注意到(坦率地说,不喜欢)我通过我的应用程序管道取消令牌。 我想出了这个“问题”的解决方案(它是,它不是)并且正在寻求意见。

首先,我们都在同一个页面上 - 让我们回顾一下请求管道:

  • 请求到达服务器,asp.net 核心框架实例化一个HttpContext
  • 框架实例化一个CancellationToken并将其附加到HttpContext.RequestAborted属性(是的,大声笑,具有CancellationToken类型)
  • 一些映射发生在框架的幕后,从 DI 容器中解析出 controller(控制器注册为服务)
  • 调用 controller 方法,由传递HttpContext实例的中间件管道装饰
  • 框架对HttpContext实例执行 model 绑定(取自路由参数、查询参数、正文、header 等),然后尝试将结果传递给映射期间发现的方法
  • 符合签名的 Model 绑定结果被注入(或通过,无论您喜欢哪个词)以及从HttpContext.RequestAborted属性中获取的 CancellationToken。

第二 - 这是我的问题的一些背景:

我使用 Autofac 进行依赖注入,并且我在某些时候了解到您可以提供依赖项(类实例)Lifetime Scopes,这将决定 class 实例的寿命。 我还开发了一个实用或直观的视角,有点不同,也因 scope 类型而异。 例子可能最好地说明了这一点:

  • 应用程序生命周期的单个实例(您可以在其中存储数据,然后随时在应用程序的其他任何地方解析它),
  • 每次注入类型时都有一个唯一的实例,因此注入的实例之间不会共享 state
  • 其他!

由于这是一个 asp.net 核心 3.1 mvc/web api,因此 Autofac 提供了一个lifetimeScope,它允许您从当前请求的上下文中解析来自容器的相同实例(换句话说,为任何给定请求执行的任何代码将解决相同的实例。不同的请求?不同的实例)。 这是 Autofac 6 中的.InstancePerLifetimeScope() scope 类型。

使用这些信息,我意识到我可以编写一个中间件,将httpContext.RequestAborted取消令牌分配给传输 class,该传输具有InstancePerLifetimeScope的生命周期 scope,然后将该传输注入直接使用该令牌的地方。 在代码库中,这不是很多地方(当然远少于所有探测到的位置)。

这是一个最小的代码集,演示了这种无铅场景的设置可能是什么样的——想象每个 class 都在自己的文件中:

public class SetCancellationTokenTransportMiddleware
{
    private readonly RequestDelegate next;

    public SetCancellationTokenTransport(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task InvokeAsync(HttpContext context, ITransportACancellationToken transport)
    {
        transport.Assign(context.RequestAborted);
        await next(context);
    }
}



// Autoface module registrations
public class GeneralModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<SomeDependency>().As<ISomeDependency>();
        builder.RegisterType<Repository>().As<IRepository>();
        builder.RegisterType<CancellationTokenTransport>().As<ITransportACancellationToken>().InstancePerLifetimeScope();
    }
}

// mvc controller - cancellation token is not injected
public class Controller
{
    private readonly ISomeDependency dep;

    public Controller(ISomeDependency dep)
    {
        this.dep = dep;
    }

    [HttpGet()]
    public async Task<Thing> Get()
    {
        return await dep.Handle();
    }
}

// This gets bypassed -- the cancellation token isn't plumbed through the handle method
public class SomeDependency : ISomeDependency
{
    private readonly Repository repository;
    
    public SomeDependency(IRepository repository)
    {
        this.repository = repository;
    {

    public async Task Handle() // no token is passed
    {
        return await repository.GetThing(); // no token is passed
    }
}

public class Repository : IRepository
{
    private readonly ITransportACancellationToken transport;
    private readonly DbContext context;

    public Repository(ITransportACancellationToken transport, DbContext context)
    {
        this.transport = transport;
        this.context = context;
    }

    public async Task<Thing> GetThing()
    {
        // The transport passes the token here - bypassing the calling class[es]

        return await context.Things.ToListAsync(transport.CancellationToken); 
    }
}

所以我的问题一般是 - 你如何看待这种直接向代币消费者(而不是他们的中介)提供取消代币的方法? 你能想到什么优点和缺点? 你有没有被这种方法烧过? 有没有人在他们的应用程序或他们工作过的应用程序中使用它?

我能想到的反对这种模式的一个论点是您需要明确剥夺取消令牌的代码路径的情况。 但是,这里的存储库在技术上也可以设计为允许人们选择是否允许取消。 使用工作单元模式,这也得到了部分缓解(传输 class 将被注入工作单元)。

我非常想听听一些有经验的观点。 当然也欢迎一般的想法。

你能想到什么优点和缺点?

唯一的优点是您的样板代码更少。

主要的缺点是您还需要了解并挂钩对令牌的所有所需更改。 如您所述,某些代码路径应忽略取消。 在某些代码路径中使用替代标记也很常见,例如,基于 Polly 的重试/计时器处理程序。 为了处理那些更复杂的情况,您的传输类型应该包含一个不可变的 CT 堆栈,而不仅仅是一个值。

CT 流也可能(理论上)不能完全模仿异步方法流,这些传输类型(在后台使用AsyncLocal<T> )本质上与之相关。 不过,这将是一种非常罕见的情况。

第二个缺点是任何类型的传输都是一种“魔法”,增加了阅读代码的心理负担。

有没有人在他们的应用程序或他们工作过的应用程序中使用它?

不是在我工作过的应用程序中,但我以前见过这个想法。

暂无
暂无

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

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