5

I have a Task which is always blocked and I have a CancellationToken passed into it which is used to cancel the task. However the Continuation task is never executed which is set to execute on Task's cancellation. The code is:

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

Later somewhere else in the code ...

_tokenSrc.Cancel();

The reason I had to use a Task for _stream.StartStream() is that this call blocks forever (an api on which I have no control, note that _stream refers to a thirdparty Api which streams data from a webservice) so I had to invoke it on another thread.

What is the best way to cancel the task?

i3arnon
  • 113,022
  • 33
  • 324
  • 344
MaYaN
  • 6,683
  • 12
  • 57
  • 109
  • Not sure what you are trying to do here... `_tokenSrc.Cancel();` is the correct way to cancel the task. However, you should also be monitoring this token for cancellation inside your task. It won't happen by itself. You are doing scary things here, by utilizing global scoped streams. There is no point in doing that, because your continuation task can happen inside the main task. Bottom line, what are you trying to do? Why do you need tasks for that? Please answer these two questions by editing your question above. – Victor Zakharov Mar 29 '14 at 19:10
  • That stream is an api that I am using which has been designed in a horrible way on which I have no control. since the call to _stream.StartStream() never comes back there is no way for me to check the status of the cnlToken in let's say a loop, how would you implement it? – MaYaN Mar 29 '14 at 19:13
  • 1
    @MaYaN: You can't use TPL cancellation if you are not able to check for cancellation. The cancellation token is only a means to implement task cancellation, it does not enforce it. Is there API to cancel the stream from a different thread maybe? –  Mar 29 '14 at 19:37
  • 1
    @jdv-JandeVaan: Thanks for the reply, I am beginning to reach the same conclusion :-) I would need to terminate the task as clean up. I could probably spin a child task which starts the stream and then periodically check for the IsCancellationRequested on the parent task. – MaYaN Mar 29 '14 at 19:42
  • @MaYaN: What Jan de Vaan said. Cancellation is meant to be cooperative, there is no way to enforce. See this as a workaround: http://stackoverflow.com/questions/4740856/aborting-a-long-running-task-in-tpl and this 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 Mar 29 '14 at 19:47
  • 1
    @Neolisk: Tnx, that was my back up plan which I was planning to avoid :-) – MaYaN Mar 29 '14 at 19:49

3 Answers3

2

[UPDATE]

I changed the code to below which fixed the problem:

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);
MaYaN
  • 6,683
  • 12
  • 57
  • 109
2

You can use the Register method on CancellationToken to register a delegate that will be invoked when cancellation is requested. In the delegate you call the method that unblocks your blocked operation.

Something like:

_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"

Owen Pauling
  • 11,349
  • 20
  • 53
  • 64
-1

How to use a cancellation token is clearly described here: http://msdn.microsoft.com/en-us/library/dd997396(v=vs.110).aspx with a suggested pattern to follow.

I'll report the example in case the page goes down:

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

There is an even more in-depth example here: http://msdn.microsoft.com/en-us/library/dd537607(v=vs.110).aspx but the first one should be enough for your scenario.

Saverio Terracciano
  • 3,885
  • 1
  • 30
  • 42