I thought that a task initialized with Task.Factory.StartNew with a canceled token should not be scheduled, run, and should end with a status of Canceled. But quite by accident in the VS 2019 on .NET 5, I have noticed that very rarely a task has a running status.
In this case, the operation inside the task is obviously not performed. Because in my example 1 is not output to the console. Can anyone explain what is actually happening?
Question. Why does the task in this example sometimes have the status running, although it obviously does not work?
UPD. Checked on VS 2022 on .NET 6. Same thing.
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}");
}
}
}
CodePudding user response:
On my machine, Running
status is never reached with your code, both with Framework 4.7.2 and with .NET 6 (VS 2019). As @JonasH suggested, I took a look at the code. Below is one possible explanation.
In the code below, ExecuteEntryUnsafe
is called by the TaskScheduler
(here the ThreadPoolTaskScheduler
) after the Task is scheduled to run. I tried with a basic custom scheduler, in this case the method called is ExecuteEntry()
with some additional securities to prevent double invocation. But the explanation remains valid.
Disclaimer: I am very far from having the reputation or knowledge as guys like Stephen Cleary, so please take this answer with some caution. If this is not correct, I'll delete it.
//---------
// 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();
}
}
}