Получить отмену заданий

Могу ли я получить CancellationToken, который был передан конструктору Task во время выполнения действия задачи. Большинство образцов выглядят следующим образом:

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

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

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

Но что, если мое действие не лямбда, а метод, помещенный в другой класс, и у меня нет прямого доступа к token? Единственный способ - передать token как состояние?

Ответы

Ответ 1

Но что, если мое действие не лямбда, а метод, помещенный в другой класс, и у меня нет прямого доступа к токену? Единственный способ - передать токен как состояние?

Да, в этом случае вам нужно будет передать токен в виде состояния или включить в какой-либо другой тип, который вы используете как состояние.

Это требуется только в том случае, если вы планируете использовать CancellationToken внутри метода. Например, если вам нужно позвонить token.ThrowIfCancellationRequested().

Если вы используете только токен, чтобы предотвратить запланированный метод, он не требуется.

Ответ 2

Можно ли получить CancellationToken, который был передан конструктору задачи во время выполнения действия задачи?

Нет, вы не можете получить его непосредственно из объекта Task, no.

Но что, если мое действие не лямбда, а метод, помещенный в другой класс, и у меня нет прямого доступа к токену? Единственный способ - передать токен как состояние?

Это два варианта, да. Есть и другие. (Возможно, не включительный список.)

  • Вы можете закрыть маркер отмены анонимным методом

  • Вы можете передать его как состояние

  • Вы можете убедиться, что экземпляр, используемый для делегата задачи, имеет поле экземпляра, которое хранится на маркере отмены или удерживается на некотором объекте, который хранится на токене и т.д.

  • Вы можете выставить токен, хотя какая-то другая более крупная область как состояние, то есть как публичное статическое поле (в большинстве случаев это плохая практика, но иногда это может быть применимо).

Ответ 3

Как утверждают другие ответы, вы можете передать токен в качестве параметра в свой метод. Однако важно помнить, что вы все равно хотите передать его в Task. Task.Factory.StartNew( () => YourMethod(token), token), например.

Это гарантирует, что:

  • Task не будет работать, если отмена произойдет до выполнения Task (это хорошая оптимизация)

  • An OperationCanceledException, вызванный вызываемым методом, правильно переводит задачу в состояние Canceled

Ответ 4

Существует очень простое решение:

    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

Вы можете получить CancellationToken, обратившись к внутренним полям с отражением.

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

Подсказка: вы можете искать такие вещи самостоятельно ILSpy.

Ответ 6

Это похоже на работу:

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

Это может быть необходимо для того, чтобы помощники задач общего назначения сохраняли CancellationToken отмененной задачи (я прибыл сюда, пытаясь заставить метод Jon Skeet WithAllExceptions сохранить токен).

Ответ 7

Когда мы смотрим на исходный код ссылки на класс Task, мы видим, что токен отмены хранится во внутреннем классе: ContingentProperties

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

Цель состоит в том, чтобы избежать доступа к этим свойствам, и эти свойства не всегда необходимы.

    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 cancellation token, if it has one
        internal Shared<CancellationTokenRegistration> m_cancellationRegistration; // Task 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;
            }
        }
    }