Este es un resultado muy probable en una máquina con una CPU de un solo núcleo. O posible en uno con una CPU de varios núcleos y también está ocupado haciendo otra cosa.
La creación de una tarea o un subproceso solo configura una estructura lógica del sistema operativo que permite la ejecución del código. El programador del sistema operativo no no inmediatamente comienza a ejecutarlo si los núcleos están ocupados, el hilo tiene que competir con todos los otros hilos que se están ejecutando en la máquina. Una sesión típica de Windows tiene mil o más. 64 veces por segundo, el kernel genera una interrupción y el planificador reevalúa lo que está sucediendo para ver si otro hilo debe dar un giro. Cualquier subproceso que no esté bloqueando (esperando algún otro subproceso para realizar un trabajo como leer un archivo o paquete de red) es elegible para ejecutarse y el planificador elige el que tiene la más alta prioridad. Algún código adicional en el programador juega con los valores de prioridad para asegurar que todos los hilos tengan una oportunidad.
Oportunidad es la palabra clave aquí. La programación de subprocesos es no determinista.
Tenga en cuenta que nunca dije nada sobre la clase Thread o Task o ThreadPool. No tienen el poder de hacer gran cosa sobre la forma en que el sistema operativo programa los hilos. Todo lo que es posible es evitar que se ejecute un hilo, el trabajo del programador del grupo de subprocesos.
La prioridad importa, puede modificar la propiedad Thread.Priority o Task.Priority para afectar el resultado. Pero no slamdunk, el programador del sistema operativo ajusta constantemente una prioridad de subprocesos desde la prioridad base que establece con esa propiedad. No puede evitar que un hilo alguna vez se ejecute teniendo otro con una prioridad más alta, por ejemplo.
Esperar que los hilos se ejecuten en un orden predecible provoca el peor tipo de error, un error de raza enhebrado. El segundo peor es el punto muerto. Son extremadamente difíciles de depurar porque dependen del tiempo y los recursos de la máquina disponibles y la carga. Solo puede asegurarse de obtener un pedido en particular escribiendo un código que lo solucione explícitamente. Lo cual haces usando una primitiva de subprocesamiento como Mutex o la palabra clave . También es notable que cuando intente agregar dicho código a su fragmento, terminará con un programa que ya no tiene concurrencia. O en otras palabras, terminarás sin tener ningún uso para una Tarea. Un hilo o tarea solo será útil si puede permitirse que se ejecute en momentos impredecibles.
+1 para indicar "La programación de subprocesos no es determinista". – Ramesh