我是不是应该始终将 CancellationToken 添加到我的控制器操作中?

Posted

技术标签:

【中文标题】我是不是应该始终将 CancellationToken 添加到我的控制器操作中?【英文标题】:Should I always add CancellationToken to my controller actions?我是否应该始终将 CancellationToken 添加到我的控制器操作中? 【发布时间】:2018-10-24 01:44:20 【问题描述】:

无论操作是否长,始终在我的操作中添加 CancellationToken 是一个好习惯吗?

我目前正在将它添加到每个操作中,我不知道它是对还是错。

[ApiController]
[Route("api/[controller]")]
public class DummiesController : ControllerBase

    private readonly AppDbContext _dbContext;

    public DummyController(AppDbContext dbContext)
    
        _dbContext = dbContext;
    

    [HttpGet("id")]
    public async Task<ActionResult<Dummy>> GetAsync(int id, CancellationToken ct) // <<----- should I always do this?
    
        var dummy = await _dbContext.Dummies.AsNoTracking().SingleOrDefaultAsync(e=>e.Id == id, ct);
        if (dummy == null) return NotFound();
        return dummy;
    

还有必要加CancellationToken ct = default(CancellationToken)吗?

【问题讨论】:

如果要使用就添加...如果您不打算取消操作,那您为什么要添加它? 传递CancellationToken 对象本身并不会神奇地取消任何东西。您必须自己取消。 @FCin 在 ASP.NET Core 控制器中执行的操作:andrewlock.net/… 好吧,我不知道。将它添加到长时间运行的方法中是合理的。如果您的方法需要大约 50 毫秒,那么它不会增加任何好处。 我不明白。这是如何基于意见的?前两个答案通过多个参考提供了很好的洞察力。第二个最佳答案解释了为什么总是为操作放置取消标记是一个坏主意,这在某些情况下非常重要。 【参考方案1】:

如果您对外部资源有任何依赖关系,则值得添加。

假设您的数据库很忙,或者您为数据库连接制定了临时错误处理/重试策略。如果最终用户在浏览器中按下停止,取消令牌将帮助任何等待操作(在您的代码中)优雅地取消。

不要考虑正常情况下的长期运行性质,而是在不太理想的情况下考虑长期运行性质,例如。如果在 Azure 中运行并且您的 SQL 数据库正在进行日常维护,并且实体框架被配置为重试自身(假设实体框架对取消令牌做出明智的响应)。

附带说明:Polly 弹性框架对外部取消令牌具有出色的支持,以协调来自外部取消的重试取消。他们的 wiki 值得一读,因为它让协调取消大开眼界:https://github.com/App-vNext/Polly/wiki

关于CancellationToken ct = default(CancellationToken),库代码可能比控制器操作更有价值。但如果您直接对控制器进行单元测试但又不想传递取消令牌,则很方便。话虽如此,如今 .net core 的 WebHostBuilder testframework 比直接测试控制器操作更容易测试。

【讨论】:

关于单元测试采用 CancellationToken 的操作方法代码,除非您正在测试取消本身,否则只需传入 CancellationToken.None。 那是真正的单元测试不应该是default(CancellationToken)应该存在的唯一原因。 考虑集成测试而不是单元测试你的控制器。 ASP.NET Core 具有强大的集成测试功能(测试路由、模型绑定、授权等)。 谢谢 Fred 是的,我完全同意,现在你不太可能想要对控制器进行单元测试,这可能是口误,我可能是指一般测试。【参考方案2】:

我是否应该始终将 CancellationToken 添加到我的控制器操作中?

没有。你不应该总是。

在 ASP.NET Core MVC 控制器中使用 CancellationTokenshttps://andrewlock.net/using-cancellationtokens-in-asp-net-core-mvc-controllers/

这是否是正确的行为取决于您的应用。 如果 请求修改状态,然后您可能不想停止执行 方法的中途。另一方面,如果请求没有 副作用,那么您可能想要停止(大概 昂贵)尽快行动。

因此,如果您有如下方法/操作(简化);

await ProcessOrder();
await UpdateInventory();

您不想在订单处理过程中取消订单,如果是这样,订单可以完成,但如果用户通过隧道,您将不会更新库存,并失去互联网连接。

当操作不能包含在类似工作单元的模式(例如分布式系统)中时,这一点尤其重要,并且应该尽量减少取消。

【讨论】:

或者您可以使用跨越 ProcessOrder 和 UpdateInventory 的事务。这样,如果请求花费的时间过长(并且客户端已为弹性配置了重试/超时策略),仍然可以取消请求,并且事务将回滚,使您的数据库处于一致状态。 如果你调用 3rd 方 api 怎么办?还是做 I/O? 那么你无论如何都不能保证一致性,除非你有一些跨服务事务系统正在进行(可能是基于事件/回调的每个服务的回滚)。 那么不需要为此使用取消令牌。运行完成。 除非您希望在没有人等待响应时取消它是一项资源密集型操作。所以我们只是说这取决于。【参考方案3】:

我认为取消令牌应该主要用于查询类型的操作。 以CRUD(Create, Read, Update, Delete)操作为例,基于CQRS(Command Query Responsibility Segregation)进行分解。

命令:创建、更新、删除 查询:阅读

如您所见,对于查询操作,随时取消都不是问题。但是对于 Command,您很可能不想取消任何操作。比如Create,假设你正在为2个独立的数据库创建数据,然后中途取消,会导致数据不同步。

【讨论】:

以上是关于我是不是应该始终将 CancellationToken 添加到我的控制器操作中?的主要内容,如果未能解决你的问题,请参考以下文章

您是不是应该始终对 C 中的数字使用“int”,即使它们是非负数?

您是不是应该始终对 C 中的数字使用“int”,即使它们是非负数?

NSFetchedResultsController:在调用 -performFetch 之前,我是不是应该始终检查 fetchedObjects==nil?

我是不是应该始终使用 Task.Delay 而不是 Thread.Sleep? [复制]

NSFetchedResultsController 是不是应该始终与 Core Data 一起使用?

GrpahQL 的 DataFetcher 中是不是应该始终使用多线程?