《Concurrency in C# Cookbook》--- 读书随记(5)
CHAPTER 10 Cancellation
《Concurrency in C# Cookbook》
Asynchronous, Parallel, and Multithreaded ProgrammingAuthor: Stephen Cleary
如果需要电子书的小伙伴,可以留下邮箱,看到了会发送的
.NET 4.0 framework引入了详尽的、设计良好的取消支持。这种支持是合作的,这意味着可以请求取消,但不能对代码强制取消。由于取消是合作的,因此不可能取消代码,除非编写代码以支持取消。出于这个原因,我建议在尽可能多的代码中支持取消
取消被视为一种特殊的错误。约定是,取消的代码将引发 OperationCanceledException 类型(或派生类型,如 TaskCanceledException)的异常。通过这种方式,调用代码知道已观察到取消操作
10.1 Issuing Cancellation Requests
Problem
您的代码调用可取消的代码(该代码接受一个取消令牌) ,您需要取消它
Solution
CancellationTokenSource 类型是 CancellationToken 的源。它只允许代码响应取消请求; CancellationTokenSource 成员允许代码请求取消
每个 CancellationTokenSource 都是独立于其他任何一个的。Token 属性为该源返回 CancellationToken,Cancel 方法发出实际的取消请求
void IssueCancelRequest()
{
using var cts = new CancellationTokenSource();
var task = CancelableMethodAsync(cts.Token);
// At this point, the operation has been started.
// Issue the cancellation request.
cts.Cancel();
}
当您取消代码时,几乎总是存在竞态条件。当发出取消请求时,可取消代码可能已经快要完成了,如果在完成之前没有检查其取消令牌,那么它实际上将成功完成。事实上,当您取消代码时,有三种可能性: 它可能响应取消请求(抛出 OperationCanceledException) ,它可能成功完成,或者它可能以与取消无关的错误结束(抛出不同的异常)
10.2 Responding to Cancellation Requests by Polling
Problem
您的代码中有一个需要支持取消的循环
Solution
如果代码中有一个处理循环,那么就没有可以向其传递 CancelationToken 的低级 API 了。在这种情况下,您应该定期检查令牌是否已被取消
public int CancelableMethod(CancellationToken cancellationToken)
{
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(1000); // Some calculation goes here.
cancellationToken.ThrowIfCancellationRequested();
}
return 42;
}
如果循环非常紧密(例如,如果循环体执行得非常快) ,那么您可能需要限制检查取消令牌的频率。像往常一样,在做出这样的改变之前和之后,先衡量一下你的表现,然后再决定哪种方式是最好的。下面的代码类似于前面的示例,但是它有更多的快速循环迭代,所以我对检查令牌的频率加了一个限制
public int CancelableMethod(CancellationToken cancellationToken)
{
for (int i = 0; i != 100000; ++i)
{
Thread.Sleep(1); // Some calculation goes here.
if (i % 1000 == 0)
cancellationToken.ThrowIfCancellationRequested();
}
return 42;
}
10.3 Canceling Due to Timeouts
Problem
有些代码需要在超时后停止运行
Solution
取消是超时情况的自然解决方案。超时只是取消请求的一种类型。需要取消的代码只是观察取消令牌,就像任何其他取消一样; 它既不应该知道也不应该关心取消源是计时器
对于基于计时器自动发出取消请求的取消令牌源,有一些方便的方法
async Task IssueTimeoutAsync()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
CancellationToken token = cts.Token;
await Task.Delay(TimeSpan.FromSeconds(10), token);
}
或者,如果您已经有一个 CcellationTokenSource 实例,则可以为该实例启动超时
async Task IssueTimeoutAsync()
{
using var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(TimeSpan.FromSeconds(5));
await Task.Delay(TimeSpan.FromSeconds(10), token);
}
10.8 Injecting Cancellation Requests
Problem
您的代码中有一层需要响应取消请求,并向下一层发出自己的取消请求
Solution
NET 4.0取消系统内置了对此场景的支持,称为链接取消令牌。可以创建链接到一个(或多个)现有令牌的取消令牌源。创建链接取消令牌源时,当取消任何现有令牌或显式取消链接源时,将取消生成的令牌
async Task<HttpResponseMessage> GetWithTimeoutAsync(HttpClient client,
string url, CancellationToken cancellationToken)
{
using CancellationTokenSource cts = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromSeconds(2));
CancellationToken combinedToken = cts.Token;
return await client.GetAsync(url, combinedToken);
}
当用户取消现有的 CancellationToken 或者当 CancelAfter 取消链接源时,将取消生成的组合令牌
尽管前面的示例只使用了一个 CcellationToken 源,但 CreateLinkedTokenSource 方法可以将任意数量的取消令牌作为参数。这使您能够创建单个组合令牌,从中可以实现逻辑取消。例如,ASP.NET 提供了一个表示用户断开连接(HttpContext)的取消令牌。处理程序代码可以创建一个链接令牌,该令牌响应用户断开连接或其自身的取消原因,如超时
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?