如何使用取消令牌在C#中取消已阻止的任务?

5

我有一个任务一直被阻塞,我传递了一个 CancellationToken 用于取消该任务。但是当任务被取消时,设置为在任务取消时执行的 Continuation 任务从未被执行。以下是代码:

    _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指向一个从webservice中流式传输数据的第三方API),因此我必须在另一个线程上调用它。

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


不确定你在这里尝试做什么... _tokenSrc.Cancel(); 是取消任务的正确方式。但是,你还应该在任务内部监视此标记以进行取消操作。它不会自动发生。你正在使用全局范围的流执行可怕的操作。没有必要这样做,因为你的继续任务可以在主任务中发生。归根结底,你想做什么?为什么需要任务?请通过编辑上面的问题回答这两个问题。 - Victor Zakharov
那个流是我正在使用的API,它的设计非常糟糕,而且我无法控制。由于对_stream.StartStream()的调用从未返回,因此我无法在循环中检查cnlToken的状态,你会如何实现它? - MaYaN
1
@MaYaN:如果您无法检查取消请求,那么您不能使用TPL取消。取消令牌只是实现任务取消的一种手段,它并不强制执行。也许有API可以从不同的线程取消流吗? - user180326
1
@jdv-JandeVaan:感谢回复,我开始得出相同的结论 :-) 我需要终止任务并进行清理。我可能可以启动一个子任务来启动流,并定期检查父任务上的IsCancellationRequested。 - MaYaN
@MaYaN:就像Jan de Vaan所说的那样。取消应该是协作的,没有强制执行的方式。可以将其视为解决方法:https://dev59.com/QMF_zogBFxS5KdRj3vPr和http://social.msdn.microsoft.com/Forums/vstudio/en-US/d0bcb415-fb1e-42e4-90f8-c43a088537fb/aborting-a-long-running-task-in-tpl?forum=parallelextensions - Victor Zakharov
1
@Neolisk:谢谢,那是我的备选计划,我原本打算避免使用它的 :-) - MaYaN
3个回答

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 "如何:为取消请求注册回调"


2

[更新]

我将代码更改为以下内容,解决了问题:

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);

-1

如何使用取消令牌在此处有清晰的描述: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但对于你的情况来说,第一个示例就足够了。

2
我知道如何使用取消令牌,问题在于一个特定的场景。 - MaYaN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接