VS调试模式下使用net core 3.1时线程排队乱序
The thread is queued disorderly when using netcore 3.1 in debug mode in VS
测试环境
.netcore 3.1(.netcore 2.x 未测试)
- 在 vs studio 中启动新的调试实例:预计不会 ❌.
- 调试构建:没问题✔。
- 发布版本:没问题✔。
.NET Framework 4.7.1(其他版本未测试).
- 在 vs studio 中启动新的调试实例:没问题✔。
- 调试构建:没问题✔。
- 发布版本:没问题✔。
可重现代码:
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 中或在没有调试器的情况下观察到的“正确”行为只是巧合,并不能保证一定会发生。所以当它没有发生时 - 这不是错误。
测试环境
.netcore 3.1(.netcore 2.x 未测试)
- 在 vs studio 中启动新的调试实例:预计不会 ❌.
- 调试构建:没问题✔。
- 发布版本:没问题✔。
.NET Framework 4.7.1(其他版本未测试).
- 在 vs studio 中启动新的调试实例:没问题✔。
- 调试构建:没问题✔。
- 发布版本:没问题✔。
可重现代码:
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 中或在没有调试器的情况下观察到的“正确”行为只是巧合,并不能保证一定会发生。所以当它没有发生时 - 这不是错误。