《Concurrency in C# Cookbook》--- 读书随记(5)

CHAPTER 10 Cancellation

《Concurrency in C# Cookbook》
Asynchronous, Parallel, and Multithreaded Programming

Author: 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)的取消令牌。处理程序代码可以创建一个链接令牌,该令牌响应用户断开连接或其自身的取消原因,如超时

posted @   huang1993  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示