获取任务取消语句

时间:2013-03-15 18:03:14

标签: c# .net multithreading cancellation-token

我可以获得在执行任务操作期间传递给CancellationToken构造函数的Task。大多数样本看起来像这样:

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

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

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

但是如果我的行为不是lambda而是放在其他类中的方法而且我没有直接访问token怎么办?唯一的方法是将token作为状态传递吗?

7 个答案:

答案 0 :(得分:11)

  

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

不,你不能直接从Task对象获取它,没有。

  

但是如果我的动作不是lambda而是放在其他类中的方法而且我没有直接访问令牌呢?唯一的方法是将令牌作为状态传递吗?

这是两个选项,是的。还有其他人。 (可能不是包容性列表。)

  1. 您可以使用匿名方法

  2. 关闭取消令牌
  3. 您可以将其作为状态传递

  4. 您可以确保用于任务委托的实例具有一个保留在取消令牌上的实例字段,或者保留一些保留在令牌上的对象等。

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

答案 1 :(得分:10)

  

但是如果我的动作不是lambda而是放在其他类中的方法而且我没有直接访问令牌呢?唯一的方法是将令牌作为状态传递吗?

是的,在这种情况下,您需要将标记框作为状态传递,或者包含在您用作状态的其他类型中。

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

如果您仅使用令牌来阻止安排该方法,则不需要。

答案 2 :(得分:8)

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

这可以确保:

  1. 如果在Task执行之前取消,则Task将无法运行(这是一个很好的优化)

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

答案 3 :(得分: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();
    }
}

答案 4 :(得分:1)

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

答案 5 :(得分:0)

这似乎可行:

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

这对于使通用Task助手保留已取消的Task的CancellationToken是必要的(我在这里试图使Jon Skeet's WithAllExceptions method保留Token时来到这里)。

答案 6 :(得分:-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;
            }
        }
    }