Task.Factory.StartNew 已取消令牌的状态为 运行

Task.Factory.StartNew with a canceled token has the status running

我认为不应安排使用 Task.Factory.StartNew 和已取消标记初始化的任务 运行,并且应该以已取消状态结束。但是在 .NET 5 上的 VS 2019 中,我注意到很少有任务具有 运行ning 状态。

在这种情况下,显然没有执行任务内部的操作。因为在我的例子中 1 并没有输出到控制台。谁能解释一下到底发生了什么?

问题。为什么本例中的任务有时状态为运行ning,虽然它显然不起作用?

UPD。在 .NET 6 上检查 VS 2022。同样的事情。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(1);
            Thread.Sleep(100);

            Task t1 = new Task(() => { }, cancellationTokenSource.Token);
            Task t2 = new Task(() => { }, cancellationTokenSource.Token);
            Task t3 = new Task(() => { }, cancellationTokenSource.Token);
            Task t4 = new Task(() => { }, cancellationTokenSource.Token);
            Task t5 = new Task(() => { }, cancellationTokenSource.Token);           
           
            Console.WriteLine($"t1: {t1.Status}");
            Console.WriteLine($"t2: {t2.Status}");
            Console.WriteLine($"t3: {t3.Status}");
            Console.WriteLine($"t4: {t4.Status}");
            Console.WriteLine($"t5: {t5.Status}");

            Task t6 = Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Console.WriteLine(1);
                }

            }, cancellationTokenSource.Token);

            Console.WriteLine($"t6: {t6.Status}");
            Console.WriteLine($"t6: {t6.Status}");
            Console.WriteLine($"t6: {t6.Status}");
            Console.WriteLine($"t6: {t6.Status}");
        }
    }
}

在我的机器上,您的代码永远不会达到 Running 状态,无论是使用 Framework 4.7.2 还是使用 .NET 6 (VS 2019)。正如@JonasH 所建议的,我看了一下代码。以下是一种可能的解释。

在下面的代码中,ExecuteEntryUnsafe 在任务安排到 运行 之后由 TaskScheduler(这里是 ThreadPoolTaskScheduler)调用。我尝试使用基本的自定义调度程序,在这种情况下,调用的方法是 ExecuteEntry() 和一些额外的安全措施以防止重复调用。但解释仍然有效。

免责声明:我远没有像 Stephen Cleary 这样的人那样享有声誉或知识,所以请谨慎对待这个答案。如果这不正确,我会删除它。

//---------
// Task.cs
//---------

internal void ExecuteEntryUnsafe(Thread? threadPoolThread)
{
    // [SOURCE comment] Remember that we started running the task delegate
    // This is what actually makes the status show as Running (see source):
    // ...
    // int sf = m_stateFlags;
    // 
    // if ((sf & TASK_STATE_FAULTED) != 0) rval = TaskStatus.Faulted;
    // else if ((sf & TASK_STATE_CANCELED) != 0) rval = TaskStatus.Canceled;
    // else if ((sf & TASK_STATE_RAN_TO_COMPLETION) != 0) rval = TaskStatus.RanToCompletion;
    // else if ((sf & TASK_STATE_WAITING_ON_CHILDREN) != 0) rval = TaskStatus.WaitingForChildrenToComplete;
    // else if ((sf & TASK_STATE_DELEGATE_INVOKED) != 0) rval = TaskStatus.Running; <-- here
    // ...

    m_stateFlags |= (int)TaskStateFlags.DelegateInvoked;

    // *** 
    // On your machine, execution probably switches back to main thread for a fraction of a second here.
    // Hence one of your WriteLines outputs "Running". But this means "delegate was invoked", not necessarily "code is executing".
    // On my machine this does not happen, so I do not see Running status.
    // *** 

    if (!IsCancellationRequested & !IsCanceled)
    {
        ExecuteWithThreadLocal(ref t_currentTask, threadPoolThread);
    }
    else
    {
        // As Cancellation is requested this will be executed, even if IsCancelled is false.
        ExecuteEntryCancellationRequestedOrCanceled(); 
    }
}

// ...

internal void ExecuteEntryCancellationRequestedOrCanceled()
{
    // If Task is cancelled; this is different that "is cancellation requested". So this block is executed.
    if (!IsCanceled) 
    {
        // Set Cancelled flag.
        int prevState = Interlocked.Exchange(ref m_stateFlags, m_stateFlags | (int)TaskStateFlags.Canceled);

        // If previous state was not yet Cancelled, perform some cleanup.
        if ((prevState & (int)TaskStateFlags.Canceled) == 0)
        {
            CancellationCleanupLogic();
        }
    }
}