32

我可以获取在任务操作执行期间CancellationToken传递给Task构造函数的内容吗?大多数示例如下所示:

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

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

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

但是,如果我的操作不是 lambda 而是放置在其他类中的方法并且我没有直接访问权限怎么办tokentoken唯一的方法是作为状态传递吗?

4

7 回答 7

14

但是,如果我的操作不是 lambda 而是放置在其他类中的方法并且我没有直接访问令牌的权限怎么办?唯一的方法是将令牌作为状态传递吗?

是的,在这种情况下,您需要传递作为状态装箱的令牌,或者包含在您用作状态的其他类型中。

但是,仅当您计划CancellationToken在方法内使用时才需要这样做。例如,如果您需要调用token.ThrowIfCancellationRequested().

如果您仅使用令牌来阻止方法被调度,那么它不是必需的。

于 2013-03-15T18:04:47.980 回答
11

我可以获得在任务操作执行期间传递给 Task 构造函数的 CancellationToken 吗?

不,您不能直接从Task对象中获取它,不。

但是,如果我的操作不是 lambda 而是放置在其他类中的方法并且我没有直接访问令牌的权限怎么办?唯一的方法是将令牌作为状态传递吗?

这是两个选项,是的。不过还有其他的。(可能不包括在内。)

  1. 您可以通过匿名方法关闭取消令牌

  2. 您可以将其作为状态传递

  3. 您可以确保用于任务委托的实例具有保存取消令牌的实例字段,或保存保存令牌的某个对象等。

  4. 您可以通过其他更大的范围将令牌公开为状态,即作为公共静态字段(在大多数情况下是不好的做法,但有时可能适用)

于 2013-03-15T18:06:01.213 回答
9

这似乎有效:

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

这对于使通用任务助手保留已取消任务的 CancellationToken 是必要的(我到达这里时试图让Jon Skeet 的 WithAllExceptions 方法保留令牌)。

于 2019-02-05T18:54:38.543 回答
8

正如其他答案所述,您可以将令牌作为参数传递给您的方法。但是,重要的是要记住您仍然希望将其传递给Taskas。Task.Factory.StartNew( () => YourMethod(token), token), 例如。

这确保:

  1. 如果在执行之前发生取消,Task则不会运行Task(这是一个很好的优化)

  2. 被调用方法抛出的OperationCanceledException正确将任务转换为Canceled状态

于 2014-11-19T20:11:43.337 回答
3

有一个非常简单的解决方案:

    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();
    }
}
于 2016-12-23T16:46:20.750 回答
2

您可以通过使用反射访问内部字段来获取 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自行搜索此类内容。

于 2016-01-11T16:37:40.237 回答
-1

当我们查看 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'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;
            }
        }
    }
于 2018-10-26T10:58:52.323 回答