张希彬 發表於 2025-12-3 09:20:34

深入解读CancellationToken: .NET异步操作的控制操作方法

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">深入解读CancellationToken:.NET异步操作的精准控制</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">一、技术背景</a></li><li><a href="#_lab2_0_1">二、核心原理</a></li><li><a href="#_lab2_0_2">三、底层实现剖析</a></li><li><a href="#_lab2_0_3">四、代码示例</a></li><ul class="third_class_ul"><li><a href="#_label3_0_3_0">(一)基础用法</a></li><li><a href="#_label3_0_3_1">(二)进阶场景</a></li><li><a href="#_label3_0_3_2">(三)避坑案例</a></li></ul><li><a href="#_lab2_0_4">五、性能对比/实践建议</a></li><ul class="third_class_ul"></ul><li><a href="#_lab2_0_5">六、常见问题解答</a></li><ul class="third_class_ul"><li><a href="#_label3_0_5_3">(一)一个CancellationToken能否用于多个异步任务?</a></li><li><a href="#_label3_0_5_4">(二)CancellationToken如何与async/await配合使用?</a></li></ul></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>深入解读CancellationToken:.NET异步操作的精准控制</h2>
<p>在.NET异步编程中,有效地控制异步操作的执行过程至关重要。<code>CancellationToken</code>提供了一种优雅的方式来取消或停止正在进行的异步操作,确保资源的合理利用,提高应用程序的稳定性和响应性,尤其适用于处理长时间运行的异步任务。</p>
<p class="maodian"><a name="_lab2_0_0"></a></p><h3>一、技术背景</h3>
<p>在异步编程模型里,一旦启动一个异步任务,如网络请求、文件读取或复杂计算,有时需要在任务完成前将其终止。例如,用户在下载大文件时改变主意取消下载,或者一个Web请求等待异步任务响应超时,需要终止任务以释放资源。传统方式实现异步操作的取消较为复杂,而<code>CancellationToken</code>为开发人员提供了一种标准、简洁的机制,使得异步操作的取消变得更加容易和安全。</p>
<p class="maodian"><a name="_lab2_0_1"></a></p><h3>二、核心原理</h3>
<p><code>CancellationToken</code>本质上是一个轻量级对象,用于向一个或多个异步操作发出取消信号。它通过一个布尔属性<code>IsCancellationRequested</code>来表示是否已请求取消。当<code>IsCancellationRequested</code>为<code>true</code>时,异步操作应尽快停止执行。</p>
<p><code>CancellationToken</code>通常与<code>CancellationTokenSource</code>配合使用。<code>CancellationTokenSource</code>负责创建和管理<code>CancellationToken</code>实例。它提供了一个<code>Cancel</code>方法,调用该方法会将关联的<code>CancellationToken</code>的<code>IsCancellationRequested</code>属性设置为<code>true</code>,从而通知所有监听该令牌的异步操作进行取消。</p>
<p class="maodian"><a name="_lab2_0_2"></a></p><h3>三、底层实现剖析</h3>
<p>从底层实现看,<code>CancellationToken</code>内部使用一个<code>int</code>类型的字段来存储取消状态。当<code>CancellationTokenSource</code>的<code>Cancel</code>方法被调用时,会通过线程安全的方式修改这个字段的值,进而影响<code>IsCancellationRequested</code>属性的返回值。</p>
<p>在异步操作中,开发人员需要定期检查<code>CancellationToken</code>的状态。许多.NET框架中的异步方法已经支持传入<code>CancellationToken</code>参数,这些方法内部会在适当的时机检查令牌状态。例如,<code>Task.Delay</code>方法的重载版本允许传入<code>CancellationToken</code>,在延迟过程中,如果令牌被取消,<code>Task.Delay</code>会提前结束延迟并抛出<code>OperationCanceledException</code>。</p>
<p>以下是<code>CancellationTokenSource</code>部分核心源码简化示意:</p>
<div class="jb51code"><pre class="brush:csharp;">public class CancellationTokenSource : CancellationTokenRegistration, IDisposable
{
    private int _m_state;
    public void Cancel()
    {
      // 通过Interlocked操作确保线程安全地修改取消状态
      Interlocked.Exchange(ref _m_state, 1);
    }
}</pre></div>
<p class="maodian"><a name="_lab2_0_3"></a></p><h3>四、代码示例</h3>
<p class="maodian"><a name="_label3_0_3_0"></a></p><h4>(一)基础用法</h4>
<ol><li><strong>功能说明</strong>:演示如何使用<code>CancellationToken</code>取消一个简单的异步延迟任务。</li></ol>
<div class="jb51code"><pre class="brush:csharp;">using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static async Task Main()
    {
      var cancellationTokenSource = new CancellationTokenSource();
      CancellationToken cancellationToken = cancellationTokenSource.Token;
      Task delayTask = Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
      // 模拟其他工作
      await Task.Delay(TimeSpan.FromSeconds(2));
      // 取消任务
      cancellationTokenSource.Cancel();
      try
      {
            await delayTask;
      }
      catch (OperationCanceledException)
      {
            Console.WriteLine("任务已取消");
      }
    }
}</pre></div>
<ol start="2"><li><strong>关键注释</strong>:创建<code>CancellationTokenSource</code>和对应的<code>CancellationToken</code>。启动一个延迟5秒的任务,并在2秒后取消任务。通过<code>try - catch</code>块捕获<code>OperationCanceledException</code>以处理任务取消的情况。</li><li><strong>运行结果</strong>:输出&ldquo;任务已取消&rdquo;。</li></ol>
<p class="maodian"><a name="_label3_0_3_1"></a></p><h4>(二)进阶场景</h4>
<ol><li><strong>功能说明</strong>:在一个模拟的长时间运行的计算任务中使用<code>CancellationToken</code>进行取消控制。</li></ol>
<div class="jb51code"><pre class="brush:csharp;">using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static async Task LongRunningCalculationAsync(CancellationToken cancellationToken)
    {
      for (int i = 0; i &lt; 1000000; i++)
      {
            if (cancellationToken.IsCancellationRequested)
            {
                Console.WriteLine("计算任务已取消");
                return;
            }
            // 模拟计算
            await Task.Delay(1);
      }
      Console.WriteLine("计算任务完成");
    }
    static async Task Main()
    {
      var cancellationTokenSource = new CancellationTokenSource();
      CancellationToken cancellationToken = cancellationTokenSource.Token;
      Task calculationTask = LongRunningCalculationAsync(cancellationToken);
      // 模拟其他工作
      await Task.Delay(TimeSpan.FromSeconds(2));
      // 取消任务
      cancellationTokenSource.Cancel();
      try
      {
            await calculationTask;
      }
      catch (OperationCanceledException)
      {
            Console.WriteLine("任务已取消,异常捕获");
      }
    }
}</pre></div>
<ol start="2"><li><strong>关键注释</strong>:<code>LongRunningCalculationAsync</code>方法模拟长时间运行的计算任务,通过循环和<code>await Task.Delay(1)</code>模拟实际计算工作,并在每次循环中检查<code>CancellationToken</code>状态。在<code>Main</code>方法中启动计算任务,2秒后取消任务,并捕获异常。</li><li><strong>运行结果</strong>:输出&ldquo;计算任务已取消&rdquo;和&ldquo;任务已取消,异常捕获&rdquo;。</li></ol>
<p class="maodian"><a name="_label3_0_3_2"></a></p><h4>(三)避坑案例</h4>
<ol><li><strong>常见错误</strong>:在异步操作中未正确检查<code>CancellationToken</code>状态。</li></ol>
<div class="jb51code"><pre class="brush:csharp;">using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static async Task IncorrectCalculationAsync(CancellationToken cancellationToken)
    {
      // 错误:未检查CancellationToken状态
      for (int i = 0; i &lt; 1000000; i++)
      {
            await Task.Delay(1);
      }
      Console.WriteLine("计算任务完成");
    }
    static async Task Main()
    {
      var cancellationTokenSource = new CancellationTokenSource();
      CancellationToken cancellationToken = cancellationTokenSource.Token;
      Task calculationTask = IncorrectCalculationAsync(cancellationToken);
      // 模拟其他工作
      await Task.Delay(TimeSpan.FromSeconds(2));
      // 取消任务
      cancellationTokenSource.Cancel();
      try
      {
            await calculationTask;
      }
      catch (OperationCanceledException)
      {
            Console.WriteLine("任务已取消,异常捕获");
      }
    }
}</pre></div>
<ol start="2"><li><strong>错误说明</strong>:<code>IncorrectCalculationAsync</code>方法在循环中没有检查<code>CancellationToken</code>状态,即使调用了<code>Cancel</code>方法,任务也不会响应取消请求,会继续执行直到完成。</li><li><strong>修复方案</strong>:在循环中添加对<code>CancellationToken</code>状态的检查。</li></ol>
<div class="jb51code"><pre class="brush:csharp;">using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static async Task CorrectCalculationAsync(CancellationToken cancellationToken)
    {
      for (int i = 0; i &lt; 1000000; i++)
      {
            if (cancellationToken.IsCancellationRequested)
            {
                Console.WriteLine("计算任务已取消");
                return;
            }
            await Task.Delay(1);
      }
      Console.WriteLine("计算任务完成");
    }
    static async Task Main()
    {
      var cancellationTokenSource = new CancellationTokenSource();
      CancellationToken cancellationToken = cancellationTokenSource.Token;
      Task calculationTask = CorrectCalculationAsync(cancellationToken);
      // 模拟其他工作
      await Task.Delay(TimeSpan.FromSeconds(2));
      // 取消任务
      cancellationTokenSource.Cancel();
      try
      {
            await calculationTask;
      }
      catch (OperationCanceledException)
      {
            Console.WriteLine("任务已取消,异常捕获");
      }
    }
}</pre></div>
<ol start="4"><li><strong>运行结果</strong>:输出&ldquo;计算任务已取消&rdquo;和&ldquo;任务已取消,异常捕获&rdquo;。</li></ol>
<p class="maodian"><a name="_lab2_0_4"></a></p><h3>五、性能对比/实践建议</h3>
<ol><li><strong>性能对比</strong>:使用<code>CancellationToken</code>带来的性能开销极小,因为它主要依赖简单的布尔值检查和线程安全的状态修改操作。相较于不使用<code>CancellationToken</code>而让任务无限制运行直至完成,适时取消任务可以显著节省资源,提高系统的整体性能和响应速度。</li><li><strong>实践建议</strong>:<ul><li>在编写长时间运行的异步任务时,始终考虑接受<code>CancellationToken</code>参数,以便调用者可以在需要时取消任务。</li><li>定期在异步任务的关键节点检查<code>CancellationToken</code>状态,确保任务能及时响应取消请求。但检查频率不宜过高,以免影响性能。</li><li>注意处理<code>OperationCanceledException</code>,确保在任务取消时资源得到正确清理,如关闭文件句柄、释放网络连接等。</li></ul></li></ol>
<p class="maodian"><a name="_lab2_0_5"></a></p><h3>六、常见问题解答</h3>
<p class="maodian"><a name="_label3_0_5_3"></a></p><h4>(一)一个CancellationToken能否用于多个异步任务?</h4>
<p>可以,一个<code>CancellationToken</code>可以传递给多个异步任务,当调用<code>CancellationTokenSource</code>的<code>Cancel</code>方法时,所有监听该<code>CancellationToken</code>的异步任务都会收到取消信号。</p>
<p class="maodian"><a name="_label3_0_5_4"></a></p><h4>(二)CancellationToken如何与async/await配合使用?</h4>
<p>在<code>async</code>方法中,将<code>CancellationToken</code>作为参数传递,并在方法内部适当位置检查<code>IsCancellationRequested</code>属性。当使用<code>await</code>等待异步操作时,如果操作支持<code>CancellationToken</code>,可将令牌传递进去,这样在令牌取消时,异步操作会提前结束并抛出<code>OperationCanceledException</code>。</p>
<p><code>CancellationToken</code>为.NET异步编程中的任务取消提供了一种简洁且高效的机制。在实际开发中,合理运用<code>CancellationToken</code>能够有效提升应用程序的稳定性和响应性,避免资源浪费。随着.NET平台的发展,预计<code>CancellationToken</code>相关的功能和使用场景将进一步优化和拓展。</p>
頁: [1]
查看完整版本: 深入解读CancellationToken: .NET异步操作的控制操作方法