VS调试模式下使用net core 3.1时线程排队乱序

The thread is queued disorderly when using netcore 3.1 in debug mode in VS

测试环境

可重现代码:

using System;
using System.Collections.Concurrent;
using System.Threading;

namespace SignalTest
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 20; i++)
            {
                new Thread(new ThreadStart(new Worker($"c{i % 3}", $"name{i}").Start)).Start();
            }
            Console.ReadKey();
        }
    }


    class Worker
    {
        public static ConcurrentDictionary<string, Semaphore> Semaphores  = new ConcurrentDictionary<string, Semaphore>();

        public string Name { get; set; }
        public string Code { get; set; }

        public int Count { get; private set; }

        public Worker(string code, string name)
        {
            Code = code;
            Name = name;

            Semaphores.TryAdd(Code, new Semaphore(1, 1));
        }

        public void Start()
        {
            while (true)
            {
                try
                {
                    WriteLog($"[{Code}][{Name}] Wait.");

                    Semaphores[Code].WaitOne();
                    WriteLog($"[{Code}][{Name}] Begin.");

                    WriteLog($"[{Code}][{Name}] Working => {++Count}!!!");
                    Thread.Sleep(500);
                }
                finally
                {
                    WriteLog($"[{Code}][{Name}] Release.");
                    Semaphores[Code].Release();
                }
            }
        }

        public void WriteLog(string msg)
        {
            if (Code == "c1")
                Console.WriteLine(msg);
        }
    }
}

所需的输出应该是所有 c1-coded 线程打印 1,然后打印 2,然后 3...,但在 dotnetcore 3.1(VS 调试实例,调试 > 启动新实例)中,结果可能不是你想要的,这是一个错误吗?

其中一个不受欢迎的输出片段可能是:

[c1][name1] Working => 4!!!
[c1][name1] Release.
[c1][name1] Wait.
[c1][name4] Begin.
[c1][name4] Working => 4!!!
[c1][name4] Release.
[c1][name7] Begin.
[c1][name7] Working => 4!!!
[c1][name4] Wait.
[c1][name7] Release.
[c1][name7] Wait.
[c1][name10] Begin.
[c1][name10] Working => 2!!!
[c1][name10] Release.
[c1][name10] Wait.
[c1][name13] Begin.
[c1][name13] Working => 1!!!
[c1][name13] Release.
[c1][name13] Wait.
[c1][name16] Begin.
[c1][name16] Working => 1!!!
[c1][name16] Release.
[c1][name16] Wait.
[c1][name19] Begin.
[c1][name19] Working => 1!!!

如所有版本的 Semaphore(和 SemaphoreSlim)中的 documentation 所述,无论是否为 .NET 核心:

There is no guaranteed order, such as FIFO or LIFO, in which blocked threads enter the semaphore.

在您的示例中,假设 [name1] 进入一个信号量,而所有其他线程都在等待。 [name1] 打印 1 并递增计数器。然后,any 个等待线程可以进入,顺序不分先后。假设是 name2。同时,name1 已经被阻塞等待互斥量。 name2 pritns 1,递增计数器。现在关键点 - 没有什么可以阻止 name1 等待互斥体再次进入 ,因为没有顺序可以保证。所以它进入,打印 2,递增计数器。那么name3可能会进入,在那个name1之后又打印3,而其他线程还在1。

因此,您在完整的 .net 中或在没有调试器的情况下观察到的“正确”行为只是巧合,并不能保证一定会发生。所以当它没有发生时 - 这不是错误。