我应该在控制器操作中始终添加CancellationToken吗?

70

无论操作是否耗时,始终在我的动作中添加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)吗?


4
如果你打算使用它,就加上去吧...如果你不打算取消操作,那么为什么要把它加上去呢? - FCin
1
传递 CancellationToken 对象本身并不能自动取消任何操作,你需要自己手动取消。 - FCin
6
在ASP.NET Core控制器操作中使用@FCin意味着:https://andrewlock.net/using-cancellationtokens-in-asp-net-core-mvc-controllers/ - Konrad
6
好的,我不知道那个。把它加入到长时间运行的方法中是合理的。如果你的方法需要 ~50ms,那么它并没有增加任何好处。 - FCin
3
我不明白这怎么是基于意见的。前两个答案提供了很好的见解,并引用了多个参考资料。第二个最佳答案解释了为什么总是为操作添加取消标记是一个坏主意,而在某些情况下这非常重要。 - Alexei - check Codidact
显示剩余8条评论
4个回答

52

不需要,在控制器操作中总是添加CancellationToken吗?

不需要。您不应该总是添加。

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

这是否是正确的行为取决于您的应用程序。 如果请求修改状态,则可能不希望在方法执行期间停止执行。另一方面,如果请求没有副作用,则可能希望尽快停止(假定是昂贵的)操作。

因此,如果您有像下面这样的方法/操作(简化版);

await ProcessOrder();
await UpdateInventory();

在订单正在处理时,您不希望取消订单,如果这样做,订单可以完成,但是如果用户通过隧道并失去Internet连接,则不会更新库存。

当操作无法包含在工作单元模式(例如分布式系统)中时,尤其重要,并且应该尽量减少取消。


4
你可以使用跨越ProcessOrder和UpdateInventory的事务,这样如果请求需要太长时间(且客户端已配置重试/超时策略以实现弹性),仍然可以取消该请求,并且事务将被回滚,使数据库保持一致状态。 - Jorn Vanloofsvelt
2
如果你正在调用第三方API,或者进行I/O操作,该怎么办? - Teoman shipahi
1
那么,如果没有某种跨服务的事务系统(也许是基于事件/回调的,每个服务都有回滚功能),你无论如何都无法保证一致性。 - Jorn Vanloofsvelt
2
除非这是一个资源密集型操作,当没有人等待响应时您希望它被取消。所以我们可以说这取决于情况。 - Jorn Vanloofsvelt
资源密集型操作应该排队,并由单独的进程执行。 - Teoman shipahi
显示剩余10条评论

24

如果您有任何对外部资源的依赖,那么添加取消令牌是值得的。

比如说,如果您的数据库很忙,或者您已经针对数据库连接设置了瞬态错误处理/重试策略。如果最终用户在浏览器中按下停止按钮,取消令牌将有助于您代码中等待的操作优雅地取消。

请将其看作不是在正常情况下长时间运行,而是在不理想的情况下长时间运行,例如在 Azure 中运行并且您的 SQL 数据库正在进行例行维护,并且假设 Entity framework 对取消令牌做出了明智的回应。

另外一点:Polly 弹性框架非常支持使用外部取消令牌协调重试取消。他们的 wiki 值得一读,因为它是一个协调取消的很好的启示:https://github.com/App-vNext/Polly/wiki

关于 CancellationToken ct = default(CancellationToken),在库代码中使用它可能比在控制器操作中更有价值。但是如果你直接对控制器进行单元测试,又不想传递取消标记,则此功能很方便。话虽如此,现在与测试控制器操作相比,.net core的WebHostBuilder testframework 更容易进行测试。


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

11
我认为取消令牌应该主要用于查询类型的操作。以 CRUD(创建、读取、更新、删除)操作为例,基于 CQRS(命令查询职责分离)进行拆分。
  • 命令:创建、更新、删除
  • 查询:读取
可以看出,对于查询操作,随时取消操作不会有问题。但是对于命令,您大概不想取消任何操作。例如创建,假设您正在为 2 个独立的数据库创建数据,然后在一半被取消,这将导致数据不同步。

-1
另一种方法是取消任何操作,而不仅仅是查询操作。
当您在Windows上复制文件并按下“取消”按钮时,操作立即停止。Web应用程序也是如此。如果用户按保存按钮,然后关闭浏览器而不等待操作完成,则保存操作可能会立即停止。
对于长时间运行的操作,UI可以显示一个带有“请稍等”文本和取消按钮的对话框。这将使行为更加用户友好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接