locked
Aborting Tasks RRS feed

  • Question

  • Is it just me or is the CancellationToken nothing short of a glorified loop abort variable that does not help if your task isn't looping but instead running just one potentially very time consuming operation that you might want to abort at some time?

    Here's a very simple example

                CancellationTokenSource src = new CancellationTokenSource();
                Task myTask = null;
                try
                {
                    myTask = Task.Factory.StartNew(() =>
                    {
                        Console.WriteLine("Task started");
                        Thread.Sleep(10000); // simulate long running operation that I have no control over
                        Console.WriteLine("Sleep complete"); 
                    }, src.Token);
                    myTask.Wait(5000);
                    if (!myTask.IsCompleted)
                    {
                        src.Cancel();
                    }
                }
                catch (AggregateException a)
                {
                    Console.Write("aggregate exception " + a.Message);
                }
                Console.ReadLine();

    The long running operation I'm simulating with sleep can be anything, it's done by a third party that I have no control over, and it can be safely aborted (e.g. I could start a separate thread, run the long running operation inside it, and abort the thread if I decide I no longer care - but apparently aborting threads is highly discouraged). What I want here is that if the operation takes longer than 5 second, my code should just kill (I mean literally kill... I'm assuming the third party code is misbehaving and rather than it returning some junk at some later point, I just want to not care about it anymore).

    So, am I missing something in the TPF or is it unable to handle this simple task?

    I can easily do it using good old thread aborts, even with

    CancellationTokenSource src = new CancellationTokenSource();
                Thread taskThread = null;
                Task myTask = null;
                try
                {
                    myTask = Task.Factory.StartNew(() =>
                    {
                        Console.WriteLine("Task started");
                        taskThread = Thread.CurrentThread;
                        Thread.Sleep(10000); // simulate long running operation that I have no control over
                        Console.WriteLine("Sleep complete"); 
                    }, src.Token);
                    myTask.Wait(5000);
                    if (!myTask.IsCompleted)
                    {
                        if (taskThread != null)
                            taskThread.Abort();
                        src.Cancel();
                    }
                }
                catch (AggregateException a)
                {
                    Console.Write("aggregate exception " + a.Message);
                }
                Console.ReadLine();
    but since we're not supposed to abort threads anymore, I'd go down the officially recommended router if there's such a thing for this scenario. (and I realize the abort code would need some more error handling to be production worthy...)
    Tuesday, February 12, 2013 10:48 AM

All replies

  • namespace CancellationWithOCE
    {
        using System;
        using System.Threading;
        using System.Threading.Tasks;
    
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Press any key to start. Press 'c' to cancel.");
                Console.ReadKey();
    
                var tokenSource = new CancellationTokenSource();
                var token = tokenSource.Token;
    
                // Store references to the tasks so that we can wait on them and 
                // observe their status after cancellation.
                Task[] tasks = new Task[10];
    
                // Request cancellation of a single task when the token source is canceled. 
                // Pass the token to the user delegate, and also to the task so it can  
                // handle the exception correctly.
                tasks[0] = Task.Factory.StartNew(() => DoSomeWork(1, token), token);
    
                // Request cancellation of a task and its children. Note the token is passed 
                // to (1) the user delegate and (2) as the second argument to StartNew, so  
                // that the task instance can correctly handle the OperationCanceledException.
                tasks[1] = Task.Factory.StartNew(() =>
                {
                    // Create some cancelable child tasks. 
                    for (int i = 2; i < 10; i++)
                    {
                        // For each child task, pass the same token 
                        // to each user delegate and to StartNew.
                        tasks[i] = Task.Factory.StartNew(iteration =>
                                    DoSomeWork((int)iteration, token), i, token);
                    }
                    // Passing the same token again to do work on the parent task.  
                    // All will be signaled by the call to tokenSource.Cancel below.
                    DoSomeWork(2, token);
                }, token);
    
                // Give the tasks a second to start.
                Thread.Sleep(1000);
    
                // Request cancellation from the UI thread. 
                if (Console.ReadKey().KeyChar == 'c')
                {
                    tokenSource.Cancel();
                    Console.WriteLine("\nTask cancellation requested.");
    
                    // Optional: Observe the change in the Status property on the task. 
                    // It is not necessary to wait on tasks that have canceled. However, 
                    // if you do wait, you must enclose the call in a try-catch block to 
                    // catch the OperationCanceledExceptions that are thrown. If you do  
                    // not wait, no OCE is thrown if the token that was passed to the  
                    // StartNew method is the same token that requested the cancellation.
    
                    #region Optional_WaitOnTasksToComplete
                    try
                    {
                        Task.WaitAll(tasks);
                    }
                    catch (AggregateException e)
                    {
                        // For demonstration purposes, show the OCE message. 
                        foreach (var v in e.InnerExceptions)
                            Console.WriteLine("msg: " + v.Message);
                    }
    
                    // Prove that the tasks are now all in a canceled state. 
                    for (int i = 0; i < tasks.Length; i++)
                        Console.WriteLine("task[{0}] status is now {1}", i, tasks[i].Status);
                    #endregion
                }
    
                // Keep the console window open while the 
                // task completes its output.
                Console.ReadLine();
            }
    
            static void DoSomeWork(int taskNum, CancellationToken ct)
            {
                // Was cancellation already requested? 
                if (ct.IsCancellationRequested)
                {
                    Console.WriteLine("We were cancelled before we got started.");
                    Console.WriteLine("Press Enter to quit.");
                    ct.ThrowIfCancellationRequested();
                }
                int maxIterations = 1000;
    
                // NOTE!!! A benign "OperationCanceledException was unhandled 
                // by user code" error might be raised here. Press F5 to continue. Or, 
                //  to avoid the error, uncheck the "Enable Just My Code" 
                // option under Tools > Options > Debugging. 
                for (int i = 0; i < maxIterations; i++)
                {
                    // Do a bit of work. Not too much. 
                    var sw = new SpinWait();
                    for (int j = 0; j < 3000; j++) sw.SpinOnce();
                    Console.WriteLine("...{0} ", taskNum);
                    if (ct.IsCancellationRequested)
                    {
                        Console.WriteLine("bye from {0}.", taskNum);
                        Console.WriteLine("\nPress Enter to quit.");
    
                        ct.ThrowIfCancellationRequested();
                    }
                }
            }
        }
    }


    Mark Answered, if it solves your question and Vote if you found it helpful.
    Rohit Arora

    Tuesday, February 12, 2013 10:56 AM
  • I see you have loops in your DoWork, so my understanding (and practical experience with the TPF) is that your code can and will abort under the following conditions and on the following lines

                // Was cancellation already requested? 
                if (ct.IsCancellationRequested)
                {
                    Console.WriteLine("We were cancelled before we got started.");
                    Console.WriteLine("Press Enter to quit.");
                    ct.ThrowIfCancellationRequested();
                }

    and

    if (ct.IsCancellationRequested)
                    {
                        Console.WriteLine("bye from {0}.", taskNum);
                        Console.WriteLine("\nPress Enter to quit.");
    
                        ct.ThrowIfCancellationRequested();
                    }

    However, it will never abort at those two lines:

                    var sw = new SpinWait();
                    for (int j = 0; j < 3000; j++) sw.SpinOnce();

    Replace your DoWork with a simple Thread.Sleep(100000) and calling Cancel on the your token source will not abort. Simulating a long running job with a loop is very misleading in this scenario, because you can abort loops (you could also abort them with a simple loop variable that's accessible from outside the task.. ), but if it's a single operation, that won't work since the code needs to hit your ct.ThrowIfCancellationRequested(); line... and if you have an operation before that that takes an excessive amount of time, you're SOL.When your potentially long running operations are something you have no control over, I prefer to able to kill the thing instead of just letting it run to the end and discarding whatever result may come back.

    @edit: I just noted you posted the example from MSDN so I know for a fact that this isn't what I'm looking for. I guess I just can't wrap my head around that the TFP would only cater to aborts of loops and not long running synchronous operations. But I guess reading Stephen Toub's reply (http://social.msdn.microsoft.com/Forums/en-US/parallelextensions/thread/d0bcb415-fb1e-42e4-90f8-c43a088537fb/) this is indeed the case :(
    Tuesday, February 12, 2013 12:20 PM
  • Hi Stephan,

    >>Simulating a long running job with a loop is very misleading in this scenario

    But a running thread is very different a sleeping thread.

    Have a nice day.


    Ghost,
    Call me ghost for short, Thanks
    To get the better answer, it should be a better question.

    Wednesday, February 13, 2013 3:16 PM
  • How so? Every task has its own thread, right? So it doesn't matter if the thread sleeps or if it sits on a socket waiting for a reply (just as an example). Essentially there's a thread not doing anything and after X seconds you know there's no point in waiting and go on without it. I found the explanation I was probably looking for here: http://social.msdn.microsoft.com/Forums/en-US/parallelextensions/thread/c2f614f6-c96c-4821-84cc-050b21aaee45 - so I guess there is a (to me very minor) point to using CancellationTokens (and I am using them everywhere where I'd want to abort), but my biggest objecting is the long time it takes to abort in case of long running operations. I have the aborts down to those pesky long running tasks that I no longer care about.

    Monday, February 18, 2013 8:53 PM
  • It seems no way to short down this time period. I believe the cancellationtoken should be very helpful, but you can also try other way: Backgroundworker. If you don't evidence that the backgroundworker is very slower than task, please turn to this component.

    Have a nice day.


    Ghost,
    Call me ghost for short, Thanks
    To get the better answer, it should be a better question.

    Tuesday, February 19, 2013 7:50 AM