0

Intro To begin with, I have already read the following articles and to my opinion they do not answer this question:

Cancellation token in Task constructor: why? What is the use of passing CancellationToken to Task Class constructor?

It's clear to me what the use is of the token parameter.

My question I tried to document the code with inline comments to give you an idea of where I'm missing how this all chains together.

The code does what I want it to do: - Write to the console every 2 seconds - Stop after 10 seconds

My question: how does the "howDoIGetFilled" parameter get the value of the "token" parameter. The second parameter is for the Task instance itself, not the Action being used. If anything isn't clear, please let me know what you want clarified.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TaskDelegatePlayground
{
    class Program
    {
        static void Main(string[] args)
        {           
            var source = new CancellationTokenSource();
            var token = source.Token;

            Action<object> action = howDoIGetFilled => TestCancellationWithToken((CancellationToken)howDoIGetFilled);
            //I understand that this works, as you pass in the token and it gets passed to the "howDoIGetFilled" variable.
            //action(token);        
            //Wrong because it's "hardcoding" the token variable now but I don't understand how the above gets filled in.
            //Action<object> action = howDoIGetFilled => TestCancellationWithToken(token);
            var task = new Task(action, token);

            task.Start();
            Thread.Sleep(10000);

            //This should cancel the operation       
            Console.WriteLine("Cancelling");                 
            source.Cancel();

            Console.WriteLine("Ended, press any key to close the application.");
            Console.ReadKey();           
        }

        public static void TestCancellationWithToken(CancellationToken token)
        {
            //no intellisense here for token / source            
            while (true)
            {
                if (token.IsCancellationRequested)
                    break;

                Console.WriteLine("test");                     
                Thread.Sleep(2000);
            }
        }
    }
}
Community
  • 1
  • 1
Starceaker
  • 631
  • 2
  • 5
  • 14

1 Answers1

1

Short Answer:

When the Task is invoked in the thread pool, the action is executed and depending on which action it is, Action or Action<T>, it will be executed with or without the object state you can optionally pass in.

Long Answer:

Looking a reference source for Task, this is how it gets invoked:

    /// <summary>
    /// The actual code which invokes the body of the task. This can be overriden in derived types.
    /// </summary>
    internal virtual void InnerInvoke()
    {
        // Invoke the delegate
        Contract.Assert(m_action != null, "Null action in InnerInvoke()");
        var action = m_action as Action;
        if (action != null)
        {
            action();
            return;
        }
        var actionWithState = m_action as Action<object>;
        if (actionWithState != null)
        {
            actionWithState(m_stateObject);
            return;
        }
        Contract.Assert(false, "Invalid m_action in Task");
    }

You can click around in reference source to find the path of both your CancellationToken and action.

But basically, the constructor calls into an internal constructor which in turn calls a TaskConstructorCore which sets up the m_stateObject and m_action (both of which can be used when the action is called).

InnerInvoke() is invoked by the method Execute() which is called in several places, but you get the idea.

I found the route of Execute, it comes from the thread pool executing the work item.

    /// <summary>
    /// IThreadPoolWorkItem override, which is the entry function for this task when the TP scheduler decides to run it.
    /// 
    /// </summary>
    [SecurityCritical]
    void IThreadPoolWorkItem.ExecuteWorkItem()
    {
        ExecuteEntry(false);
    }

Interestingly there is a short circuit for if the cancellation token is already cancelled before the Task is queued up.

Community
  • 1
  • 1
Callum Linington
  • 14,213
  • 12
  • 75
  • 154
  • That answers it, thank you. I'm pretty sure elaborate discussions can be had regarding the fact that this is "nicely" encapsulated and thus far from intuitive. It's very clear now though, thank you for your effort! – Starceaker Aug 02 '16 at 14:23