Get Task CancellationToken

2019-03-23 07:39发布

问题:

Can I get CancellationToken which was passed to Task constructor during task action executing. Most of samples look like this:

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

Task myTask = Task.Factory.StartNew(() =>
{
    for (...)
    {
        token.ThrowIfCancellationRequested();

        // Body of for loop.
    }
}, token);

But what if my action is not lambda but a method placed in other class and I don't have direct access to token? Is the only way is to pass token as state?

回答1:

But what if my action is not lambda but a method placed in other class and I don't have direct access to token? Is the only way is to pass token as state?

Yes, in that case, you would need to pass the token boxed as state, or included in some other type you use as state.

This is only required if you plan to use the CancellationToken within the method, however. For example, if you need to call token.ThrowIfCancellationRequested().

If you're only using the token to prevent the method from being scheduled, then it's not required.



回答2:

Can I get CancellationToken which was passed to Task constructor during task action executing?

No, you can't get it directly from the Task object, no.

But what if my action is not lambda but a method placed in other class and I don't have direct access to token? Is the only way is to pass token as state?

Those are two of the options, yes. There are others though. (Possibly not an inclusive list.)

  1. You can close over the cancellation token in an anonymous method

  2. You can pass it in as state

  3. You can ensure that the instance used for the task's delegate has an instance field that holds onto the cancellation token, or holds onto some object which holds onto the token, etc.

  4. You can expose the token though some other larger scope as state, i.e. as a public static field (bad practice in most cases, but it might occasionally be applicable)



回答3:

As other answers state, you can pass the token as a parameter to your method. However, it's important to remember that you still want to pass it to the Task as well. Task.Factory.StartNew( () => YourMethod(token), token), for example.

This insures that:

  1. The Task will not run if cancellation occurs before the Task executes (this is a nice optimization)

  2. An OperationCanceledException thrown by the called method correctly transitions the Task to a Canceled state



回答4:

There is a very simple solution:

    class CancelingTasks
{
    private static void Foo(CancellationToken token)
    {
        while (true)
        {
            token.ThrowIfCancellationRequested();

            Thread.Sleep(100);
            Console.Write(".");                
        }
    }

    static void Main(string[] args)
    {
        CancellationTokenSource source = new CancellationTokenSource();
        CancellationToken tok = source.Token;

        tok.Register(() =>
        {
            Console.WriteLine("Cancelled.");
        });

        Task t = new Task(() =>
        {
            Foo(tok);
        }, tok);

        t.Start();

        Console.ReadKey();
        source.Cancel();
        source.Dispose();

        Console.WriteLine("Main program done, press any key.");
        Console.ReadKey();
    }
}


回答5:

You can get the CancellationToken by accessing internal fields with reflection.

public CancellationToken GetCancellationToken(Task task)
{
    object m_contingentProperties = task
        .GetType()
        .GetField("m_contingentProperties",
            BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
        .GetValue(task);

    object m_cancellationToken = m_contingentProperties
        .GetType()
        .GetField("m_cancellationToken",
            BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
        .GetValue(m_contingentProperties);

    return (CancellationToken)m_cancellationToken;
}

Hint: You can search for such things on your own with ILSpy .



回答6:

This seems to work:

public static CancellationToken GetCancellationToken(this Task task)
{
  return new TaskCanceledException(task).CancellationToken;
}

This can be necessary to make general-purpose Task helpers preserve the CancellationToken of a cancelled Task (I arrived here while trying to make Jon Skeet's WithAllExceptions method preserve the Token).



回答7:

When we look at the Task class reference source code we can see that the cancellation token is stored inside an internal class: ContingentProperties

https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,90a9f91ddd80b5cc

The purpose is to avoid the access of these properties and those properties are not always necessary.

    internal class ContingentProperties
    {
        // Additional context

        internal ExecutionContext m_capturedContext; // The execution context to run the task within, if any.

        // Completion fields (exceptions and event)

        internal volatile ManualResetEventSlim m_completionEvent; // Lazily created if waiting is required.
        internal volatile TaskExceptionHolder m_exceptionsHolder; // Tracks exceptions, if any have occurred

        // Cancellation fields (token, registration, and internally requested)

        internal CancellationToken m_cancellationToken; // Task's cancellation token, if it has one
        internal Shared<CancellationTokenRegistration> m_cancellationRegistration; // Task's registration with the cancellation token
        internal volatile int m_internalCancellationRequested; // Its own field because threads legally ---- to set it.

        // Parenting fields

        // # of active children + 1 (for this task itself).
        // Used for ensuring all children are done before this task can complete
        // The extra count helps prevent the ---- for executing the final state transition
        // (i.e. whether the last child or this task itself should call FinishStageTwo())
        internal volatile int m_completionCountdown = 1;
        // A list of child tasks that threw an exception (TCEs don't count),
        // but haven't yet been waited on by the parent, lazily initialized.
        internal volatile List<Task> m_exceptionalChildren;

        /// <summary>
        /// Sets the internal completion event.
        /// </summary>
        internal void SetCompleted()
        {
            var mres = m_completionEvent;
            if (mres != null) mres.Set();
        }

        /// <summary>
        /// Checks if we registered a CT callback during construction, and deregisters it. 
        /// This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed
        /// successfully or with an exception.
        /// </summary>
        internal void DeregisterCancellationCallback()
        {
            if (m_cancellationRegistration != null)
            {
                // Harden against ODEs thrown from disposing of the CTR.
                // Since the task has already been put into a final state by the time this
                // is called, all we can do here is suppress the exception.
                try { m_cancellationRegistration.Value.Dispose(); }
                catch (ObjectDisposedException) { }
                m_cancellationRegistration = null;
            }
        }
    }