【问题标题】:How do I cancel a Blocked Task in C# using a Cancellation Token?如何使用取消令牌取消 C# 中的阻塞任务?
【发布时间】:2014-03-29 19:04:06
【问题描述】:

我有一个始终被阻止的任务,并且我有一个 CancellationToken 传递给它,用于取消任务。但是,Continuation 任务永远不会执行,它设置为在 Task 取消时执行。代码是:

    _tokenSrc = new CancellationTokenSource();
    var cnlToken = _tokenSrc.Token;

    Task.Run(() => 
          // _stream.StartStream() blocks forever  
          _stream.StartStream(), cnlToken)
        .ContinueWith(ant =>
        {
            _logger.Warn("Stream task cancellation requested, stopping the stream");
            _stream.StopStream();
            _stream = null;
            _logger.Warn("Stream stopped and task cancelled");
        }, TaskContinuationOptions.OnlyOnCanceled);

稍后在代码的其他地方...

_tokenSrc.Cancel();

我必须为 _stream.StartStream() 使用任务的原因是这个调用永远阻塞(我无法控制的 api,请注意 _stream 是指从 web 服务流数据的第三方 Api)所以我必须在另一个线程上调用它。

取消任务的最佳方法是什么?

【问题讨论】:

  • 不确定您要在这里做什么..._tokenSrc.Cancel(); 是取消任务的正确方法。但是,您还应该监视此令牌以在您的任务中取消。它不会自己发生。通过使用全局范围的流,您在这里做着可怕的事情。这样做没有意义,因为您的延续任务可能发生在主任务中。归根结底,你想做什么?为什么你需要任务?请通过编辑上面的问题来回答这两个问题。
  • 该流是我正在使用的一个 API,它的设计方式很糟糕,我无法控制。由于对 _stream.StartStream() 的调用永远不会返回,因此我无法在循环中检查 cnlToken 的状态,您将如何实现它?
  • @MaYaN:如果您无法检查取消,则不能使用 TPL 取消。取消令牌只是实现任务取消的一种手段,它并不强制执行。是否有 API 可以取消来自不同线程的流?
  • @jdv-JandeVaan:感谢您的回复,我开始得出相同的结论 :-) 我需要终止清理任务。我可能会旋转一个启动流的子任务,然后定期检查父任务上的 IsCancellationRequested。
  • @Neolisk:Tnx,这是我打算避免的备用计划:-)

标签: c# task-parallel-library cancellation-token


【解决方案1】:

[更新]

我将代码更改为以下解决问题的代码:

Task.Run(() =>
{
    var innerTask = Task.Run(() => _stream.StartStream(), cToken);
    innerTask.Wait(cToken);
}, cToken)
.ContinueWith(ant =>
{
    _logger.Warn("Stream task cancellation requested, stopping the stream");
    _stream.StopStream();
    _stream = null;
    _logger.Warn("Stream stopped and task cancelled");
}, TaskContinuationOptions.OnlyOnCanceled);

【讨论】:

    【解决方案2】:

    您可以使用CancellationToken 上的Register 方法注册一个委托,该委托将在请求取消时被调用。在委托中,您调用解除阻塞操作的方法。

    类似:

    _tokenSrc = new CancellationTokenSource();
    var cnlToken = _tokenSrc.Token;
    
    var task = Task.Factory.StartNew(() =>
    {
        using(var tokenReg = cnlToken.Register(() => 
            {
                _logger.Warn("Stream task cancellation requested, stopping the stream");
                _stream.StopStream();
                _stream = null;
                _logger.Warn("Stream stopped and task cancelled");
            }))
        {
            _stream.StartStream(), cnlToken)
        }
    }, cnlToken);
    

    MSDN "How to: Register Callbacks for Cancellation Requests"

    【讨论】:

      【解决方案3】:

      此处清楚地描述了如何使用取消令牌:http://msdn.microsoft.com/en-us/library/dd997396(v=vs.110).aspx 并附有建议的模式。

      如果页面出现故障,我会报告示例:

      using System;
      using System.Threading;
      using System.Threading.Tasks;
      class Program
      {
          static void Main()
          {
      
              var tokenSource2 = new CancellationTokenSource();
              CancellationToken ct = tokenSource2.Token;
      
              var task = Task.Factory.StartNew(() =>
              {
      
                  // Were we already canceled?
                  ct.ThrowIfCancellationRequested();
      
                  bool moreToDo = true;
                  while (moreToDo)
                  {
                      // Poll on this property if you have to do 
                      // other cleanup before throwing. 
                      if (ct.IsCancellationRequested)
                      {
                          // Clean up here, then...
                          ct.ThrowIfCancellationRequested();
                      }
      
                  }
              }, tokenSource2.Token); // Pass same token to StartNew.
      
              tokenSource2.Cancel();
      
              // Just continue on this thread, or Wait/WaitAll with try-catch: 
              try
              {
                  task.Wait();
              }
              catch (AggregateException e)
              {
                  foreach (var v in e.InnerExceptions)
                      Console.WriteLine(e.Message + " " + v.Message);
              }
      
              Console.ReadKey();
          }
      }
      

      这里有一个更深入的示例:http://msdn.microsoft.com/en-us/library/dd537607(v=vs.110).aspx,但第一个对于您的场景应该足够了。

      【讨论】:

      • 我知道取消令牌是如何使用的,问题是针对特定场景的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多