作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Davit Asryan's profile image

Davit Asryan

Davit是一名软件工程师,在为公司创建企业软件方面拥有广泛的经验, including Veritas. His expertise is developing with .NET and C#.

Expertise

Previously At

Veritas
Share

有时候取消是一件好事. In many of my .NET 我有足够的动机取消内部和外部流程. 微软了解到开发人员正在以各种复杂的实现接近这个通用用例,并决定必须有一个更好的方法. Thus, a common 对消通信方式 was introduced as CancellationToken,它是使用低级多线程和进程间通信构造构建的. 作为我对这种模式的初步研究的一部分,在挖掘了实际的 .NET源代码的微软的实现-我发现 CancellationToken 能否解决更广泛的问题:应用程序运行状态的订阅, 使用不同的触发器计时操作, 以及通过标志进行的一般进程间通信.

预期的CancellationToken用例

CancellationToken was introduced in .NET 4作为一种手段来增强和标准化现有的取消操作的解决方案. 有四种处理取消的一般方法 popular programming languages tend to implement:

 KillTell, don’t take no for an answer礼貌地提出要求,并接受拒绝礼貌地设置旗帜,如果它愿意,让它轮询
ApproachHard stop; resolve inconsistencies later叫它停下来,但让它自己清理直接但温和地要求停止让它停下来,但不要强迫它
Summary一条通向腐败和痛苦的道路允许干净的停止点,但必须停止允许干净的停止点,但取消请求可能被忽略通过标志请求取消
Pthreads pthread_kill,
pthread_cancel (async)
pthread_cancel (deferred mode)n/aThrough a flag
.NETThread.Abortn/aThread.InterruptThrough a flag in CancellationToken
Java Thread.destroy,
Thread.stop
n/aThread.interruptThrough a flag or Thread.interrupted
PythonPyThreadState_SetAsyncExcn/aasyncio.Task.cancelThrough a flag
GuidanceUnacceptable; avoid this approach可以接受,特别是当语言不支持异常或展开时如果语言支持,可以接受更好,但更多的是集体努力
取消方法总结和语言例子

CancellationToken 驻留在最后一个类别中,其中取消对话是合作的.

After Microsoft introduced CancellationToken, the development community 很快就接受了它,特别是因为许多主要 .NET api被更新为本地使用这些令牌. For example, beginning with ASP.NET Core 2.0, actions support an optional CancellationToken 参数,该参数可能表示HTTP请求是否已关闭, 允许取消任何操作,从而避免不必要的资源使用.

After a deep dive into the .. NET代码库,很明显 CancellationToken的用法不限于取消.

显微镜下的CancellationToken

When looking more closely at CancellationToken的实现,我们看到它只是一个简单的标志(i.e., ManualResetEvent)以及提供监控和更改该标志的能力的支持基础设施. CancellationToken的主要实用程序在其名称中,这表明这是取消操作的常用方法. Nowadays, any .NET library, package, 或具有异步或长时间运行操作的框架允许通过这些令牌取消操作.

CancellationToken 可能触发手动设置其标志为“真”或编程它改变为“真”后,一定的时间跨度已经过去了. Regardless of how a CancellationToken is triggered, 监控该令牌的客户端代码可以通过以下三种方法之一确定令牌标志的值:

  • Using a WaitHandle
  • Polling the CancellationToken’s flag
  • 当通过编程式订阅更新标志的状态时,通知客户端代码

After further research in the .. NET代码库,很明显 .NET team found CancellationTokens 在其他未连接到取消的场景中非常有用. 让我们来探索一些高级的和非品牌的用例,它们赋予 C# developers 使用多线程和进程间协调来简化复杂的情况.

高级事件的CancellationTokens

When writing ASP.NET Core applications, 有时我们需要知道应用程序何时启动, 或者我们需要将代码注入到主机关闭过程中. In those cases, we use the IHostApplicationLifetime interface (previously IApplicationLifetime). This interface (from .NET Core’s repository) makes use of CancellationToken 沟通三大事件: ApplicationStarted, ApplicationStopping, and ApplicationStopped:

namespace Microsoft.Extensions.Hosting
{
    /// 
    ///允许消费者收到应用生命周期事件的通知. 
    ///这个接口不是用户可替换的.
    /// 
    公共接口IHostApplicationLifetime
    {
        /// 
        ///当应用主机完全启动时触发.
        /// 
        CancellationToken ApplicationStarted { get; }

        /// 
        ///当应用程序主机开始正常关机时触发.
        ///在所有回调函数注册之前,Shutdown将被阻塞 
        /// this token have completed.
        /// 
        CancellationToken ApplicationStopping { get; }

        /// 
        ///当应用程序主机完成安全关闭时触发.
        ///应用程序将不会退出,直到所有回调被注册 
        /// this token have completed.
        /// 
        CancellationToken ApplicationStopped { get; }

        /// 
        ///请求终止当前应用程序.
        /// 
        void StopApplication();
    }
}

At first glance, it may seem like CancellationTokenS不属于这里,特别是因为它们被用作事件. 然而,进一步的研究表明,这些代币是完美的匹配:

  • 它们是灵活的,允许接口的客户端以多种方式侦听这些事件.
  • 它们是开箱即用的线程安全的.
  • 它们可以通过组合从不同的来源创建 CancellationTokens.

Although CancellationTokenS并不适合所有的活动需求, 它们是只发生一次的事件的理想选择, like application start or stop.

CancellationToken for Timeout

By default, ASP.NET给我们很少的时间来关闭. 在那些我们想要更多时间的情况下,使用内置的 HostOptions class 允许我们更改这个超时值. 在下面,这个超时值被包装在 CancellationToken 并输入到底层子过程中.

IHostedService’s StopAsync Method是这种用法的一个很好的例子:

namespace Microsoft.Extensions.Hosting
{
    /// 
    ///定义由主机管理的对象的方法.
    /// 
    public interface IHostedService
    {
        /// 
        ///当应用主机准备好启动服务时触发.
        /// 
        /// Indicates that the start
        ///     process has been aborted.
        Task StartAsync(CancellationToken CancellationToken);

        /// 
        ///当应用程序主机执行安全关闭时触发.
        /// 
        /// Indicates that the shutdown 
        ///进程不再是优雅的.
        Task StopAsync(CancellationToken CancellationToken);
    }
}

As evident in the IHostedService interface definition, the StopAsync method takes one CancellationToken parameter. 与该参数相关的注释清楚地传达了微软的初始意图 CancellationToken 是一个超时机制,而不是一个取消过程.

在我看来,如果这个接口在 CancellationToken它可以是a TimeSpan 参数-指示允许处理多长时间的停止操作. 根据我的经验,超时场景几乎总是可以转换为 CancellationToken with great additional utility.

现在,让我们忘记我们知道如何 StopAsync 方法的设计,而是考虑如何设计该方法的契约. 首先让我们定义需求:

  • The StopAsync 方法必须尝试停止服务.
  • The StopAsync 方法应具有合适的停止状态.
  • 不管是否达到优雅停止状态, 托管服务必须有停止的最大时间, 由timeout参数定义.

By having a StopAsync 任何形式的方法,我们都满足第一个条件. 剩下的要求很棘手. CancellationToken 通过使用标准精确地满足这些要求 .基于。NET标志的通信工具,以增强对话.

CancellationToken作为通知机制

The biggest secret behind CancellationToken is that it’s just a flag. Let’s illustrate how CancellationToken 可以用来启动进程而不是停止进程吗.

Consider the following:

  1. Create a RandomWorker class.
  2. RandomWorker should have a DoWorkAsync 方法,该方法执行一些随机工作.
  3. The DoWorkAsync 方法必须允许调用者指定工作何时开始.
public class RandomWorker
{
    public RandomWorker(int id)
    {
        Id = id;
    }

    public int Id { get; }

    public async Task DoWorkAsync()
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"[Worker {Id}] Iteration {i}");
            await Task.Delay(1000);
        }
    }
}

上面的类满足前两个要求,剩下第三个要求. 我们可以使用几个替代接口来触发worker, 比如一个时间跨度或一个简单的旗帜:

# With a time span
任务DoWorkAsync(TimeSpan startAfter);

# Or a simple flag
bool ShouldStart { get; set; }
Task DoWorkAsync();

这两种方法都很好,但是没有什么比使用 CancellationToken:

public class RandomWorker
{
    public RandomWorker(int id)
    {
        Id = id;
    }

    public int Id { get; }

    DoWorkAsync(CancellationToken)
    {
        startToken.WaitHandle.WaitOne();

        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"[Worker {Id}] Iteration {i}");
            await Task.Delay(1000);
        }
    }
}

下面的示例客户端代码演示了这种设计的威力:

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CancelToStart
{
    public class Program
    {
        static void Main(string[] args)
        {
            CancellationTokenSource = new CancellationTokenSource();

            startCts.CancelAfter(TimeSpan.FromSeconds(10));

            var tasks = Enumerable.Range(0, 10)
                .Select(i => new RandomWorker(i))
                .Select(worker => worker.DoWorkAsync(startCts.Token))
                .ToArray();

            Task.WaitAll(tasks, CancellationToken.None);
        }
    }
}

The CancellationTokenSource will create our CancellationToken 在幕后协调所有相关流程的触发. 在本例中,关联的进程是 RandomWorker, which is waiting to start. 这种方法允许我们利用默认设置中的线程安全性 CancellationToken implementation.

一个扩展的CancellationToken工具箱

These examples demonstrate how CancellationToken 提供解决方案工具箱,这些解决方案在其预期用例之外是有用的. 这些工具可以在许多涉及基于进程间标志通信的场景中派上用场. 我们是否面临暂停, notifications, or one-time events, we can fall back on this elegant, Microsoft-tested implementation.

From top to bottom, the words "Gold" (colored gold), "Microsoft,和“合作伙伴”(都是黑色的)后面跟着微软的标志.

Understanding the basics

  • What is CancellationToken?

    CancellationToken是本地轻量级的 .用于启用协作取消的。NET构造.

  • Can you reuse CancellationTokens?

    是的,您可以重用CancellationTokens. CancellationTokenSource用于取消一组进程. 与特定CancellationTokenSource关联的所有进程将在其中使用一个CancellationToken.

  • 你如何处理CancellationToken?

    因为取消是合作的, CancellationToken处理留给传递给它的方法. Generally, 我们要么将CancellationToken传递给其他方法,要么在令牌被取消后尽快停止.

  • How can I get a CancellationToken?

    CancellationToken由CancellationTokenSource创建. 只有创建令牌的CancellationTokenSource才能取消它.

聘请Toptal这方面的专家.
Hire Now
Davit Asryan's profile image
Davit Asryan

Located in Yerevan, Armenia

Member since March 3, 2022

About the author

Davit是一名软件工程师,在为公司创建企业软件方面拥有广泛的经验, including Veritas. His expertise is developing with .NET and C#.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

Previously At

Veritas

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.