文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Dotnet线程取消的深度进阶

2024-12-02 05:52

关注

取消则不同。

通常,取消是由其它代码发出的命令,也就是说,是由一些代码去请求取消,另一部分代码的响应取消。而且,实际发生的情况,是请求代码只是通知响应代码,希望它能停止执行;响应代码会按照自己设定的方式对取消请求做出响应,有可能立即停止任务,也有可能继续运行下去,直到一个可以停止的点,甚至可能完全忽略这个取消请求。

概念清楚了,怎么做?

取消令牌

既然是一方请求,另一方响应,那对于响应代码来说,重要的是能够知道并响应取消请求。

在 Dotnet 里,给出了一个东西,叫取消令牌 ( Cancellation Tokens )。这个令牌,就是请求取消的载体。

请求代码发起取消时,实际是发起了一个对「取消令牌」的取消操作,然后,响应代码将对这个被取消的令牌做出正确反应。

如果看到这儿有点混乱的话,看一下示例代码:

async Task SomethingAsync(int data, CancellationToken cancellationToken)
{
var result = await FirstStepAsync(data, cancellationToken);
await SecondStepAsync(intermediateValue, cancellationToken);
}

响应代码基本都是这个样子。这里面,CancellationToken 就是上面说的取消令牌。

CancellationToken 可以在任何地方被设置为取消:用户按下取消按钮,或客户端断开连接,超时,等等。重要的是,当它被设置为取消时,就表示响应代码需要处理取消了。

注意:一个 CancellationToken 只能被取消一次。一旦它被取消,就会永远保持取消状态。

带有取消令牌的方法定义

上面的示例,就是一个典型的带有取消令牌的方法定义。

按照微软的习惯,带有 CancellationToken 的方法有以下约定:

当然,这是一个非强制的约定。如果你不介意别人看着别扭,可以不管这个约定。

看几个例子:

Task SomethingAsync(int data) => SomethingAsync(data, CancellationToken.None);

async Task SomethingAsync(int data, CancellationToken cancellationToken)
{
...
}

async Task SomethingAsync(int data, CancellationToken cancellationToken = default)
{
...
}

在这里,CancellationToken 代表任何类型或任何原因的取消。

通过 CancellationToken 参数,方法声明了自己可以响应取消。而实际上,这只是个声明。代码中,CancellationToken 可能会被忽略。因此,有这个声明仅仅表示方法可能支持取消,而不是一定支持。

方法对取消的响应

上面说到了,响应代码可以响应取消,也可以不取消。

而即使响应代码真的去响应取消,通常也会有不同的情况。

通常来说,如果取消请求到达时,响应方法实际取消了一些工作,会抛出 OperationCanceledException 来通知调用程序;而如果取消被忽略,或者取消请求来的太晚而任务已经完成,那响应方法会正常返回,而且不抛出 OperationCanceledException 异常。这个在微软的基础类库(BCL)中,体现得很明显。

大多数情况下,异常会被逐层传出。再看一下上面的例子:

async Task SomethingAsync(int data, CancellationToken cancellationToken)
{
var result = await FirstStepAsync(data, cancellationToken);
await SecondStepAsync(intermediateValue, cancellationToken);
}

如果 FirstStepAsync 或 SecondStepAsync 抛出 OperationCanceledException,那这个异常也会从 SomethingAsync 中传出给调用者。

这里要强调一下:看过很多代码,在请求取消时会不抛出异常而直接返回。不要这样做。调用者不知道这个取消是被接受,还是被忽略,会出大问题的。

一个常见的错误用法

在代码 Review 时,见过好几次这样的情况:

async Task SomethingAsync(CancellationToken cancellationToken)
{
var test = await Task.Run(() =>
{
...
}, cancellationToken);
...
}
// 注意,这个例子的写法是错的。

这个有必要专门拿出来说一下。

很多人把委托和 CancellationToken 传递给 Task,期望在令牌取消时取消委托。注意,这个理解是错的。

Task.Run 是对线程池的委托调度,是一个立即完成的瞬时动作。CancellationToken 在这儿的作用是取消调度这个动作,而这个动作是立即完成的,换句说说,一旦走到这一行,调度操作会立即完成,这个取消令牌也就没有用了,会被忽略。

所以,这种情况不需要用 CancellationToken,要写成下面的方式:

async Task SomethingAsync(CancellationToken cancellationToken)
{
var test = await Task.Run(( cancellationToken ) =>
{
...
});
...
}

写成这样,才是正确的表达,表达委托本身需要响应令牌。

这是一个容易搞错的知识点,记一下。

来源:老王Plus内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯