了解 SemaphoreSlim 问题?
Understanding SemaphoreSlim problems?
我这里有一个简单的测试代码与 SemaphoreSlim
static SemaphoreSlim mSemaphore = new SemaphoreSlim(3);
static async Task Main()
{
var tasks = new Task[5];
for (var i = 0; i < tasks.Length; i++)
{
var taskNo = i;
tasks[i] = Task.Run(() => DoWork($"task{taskNo}"));
}
await Task.WhenAll(tasks);
}
static async Task DoWork(string taskName)
{
for (var i = 0; i < 3; i++)
{
mSemaphore.Wait();
Console.WriteLine($"{taskName}: doing {i}.");
await Task.Delay(1000);
mSemaphore.Release();
}
}
如果我是正确的:在我的上下文中,我的信号量应该只允许 3 个任务完成它们的工作,然后它应该释放它们,然后它应该让我的其他 2 个任务完成它们的工作。
问题
我对此进行了测试,但不幸的是有时我会得到 different/wrong 结果。
这里有2个输出结果。
输出
对 mSemaphore.Wait()
和 mSemaphore.Release()
的调用在 内部 您的 for
循环中。每次循环迭代后,每个任务都会释放信号量,然后再次尝试获取它。
鉴于此,没有什么可以阻止任务 0、1 或 2 在循环结束时释放信号量,以及任务 3 获取信号量。释放它的任务将回到循环的开始,并再次坐在 mSemaphore.Wait()
上,等待另一个任务释放信号量。
您的所有任务同时 运行(除了可能有非常小的初始延迟),并且它们都具有相同的优先级(SemaphoreSlim 不保证等待的顺序)信号量获取它——它基本上是随机的等待任务将被赋予它)。因此,有时任务 0、1 和 2 在任务 3 获得信号量之前完成,有时事情以不同的顺序发生也就不足为奇了。
如果在每个任务尝试获取信号量、实际获取信号量、然后释放信号量时添加一些额外的日志记录,这应该会使它更清楚一些——您将能够看到一个任务释放它,然后另一个任务立即将其接走)。
Blocks the current thread until it can enter the SemaphoreSlim
.
Asynchronously waits to enter the SemaphoreSlim
.
由于您的代码是异步的,因此您应该使用 WaitAsync
,而不是 Wait
。通过在 运行 异步代码时阻塞线程,您将获得各种有趣的效果,而不是您期望的行为。例如,在 await Task.Delay(1000)
之后,您的异步工作流可能会在不同的线程上继续 运行,也可能不会继续,具体取决于您无法控制的条件。
长话短说,只需将 mSemaphore.Wait()
替换为 await mSemaphore.WaitAsync()
就可以了。
我这里有一个简单的测试代码与 SemaphoreSlim
static SemaphoreSlim mSemaphore = new SemaphoreSlim(3);
static async Task Main()
{
var tasks = new Task[5];
for (var i = 0; i < tasks.Length; i++)
{
var taskNo = i;
tasks[i] = Task.Run(() => DoWork($"task{taskNo}"));
}
await Task.WhenAll(tasks);
}
static async Task DoWork(string taskName)
{
for (var i = 0; i < 3; i++)
{
mSemaphore.Wait();
Console.WriteLine($"{taskName}: doing {i}.");
await Task.Delay(1000);
mSemaphore.Release();
}
}
如果我是正确的:在我的上下文中,我的信号量应该只允许 3 个任务完成它们的工作,然后它应该释放它们,然后它应该让我的其他 2 个任务完成它们的工作。
问题
我对此进行了测试,但不幸的是有时我会得到 different/wrong 结果。
这里有2个输出结果。
输出
对 mSemaphore.Wait()
和 mSemaphore.Release()
的调用在 内部 您的 for
循环中。每次循环迭代后,每个任务都会释放信号量,然后再次尝试获取它。
鉴于此,没有什么可以阻止任务 0、1 或 2 在循环结束时释放信号量,以及任务 3 获取信号量。释放它的任务将回到循环的开始,并再次坐在 mSemaphore.Wait()
上,等待另一个任务释放信号量。
您的所有任务同时 运行(除了可能有非常小的初始延迟),并且它们都具有相同的优先级(SemaphoreSlim 不保证等待的顺序)信号量获取它——它基本上是随机的等待任务将被赋予它)。因此,有时任务 0、1 和 2 在任务 3 获得信号量之前完成,有时事情以不同的顺序发生也就不足为奇了。
如果在每个任务尝试获取信号量、实际获取信号量、然后释放信号量时添加一些额外的日志记录,这应该会使它更清楚一些——您将能够看到一个任务释放它,然后另一个任务立即将其接走)。
Blocks the current thread until it can enter the
SemaphoreSlim
.
Asynchronously waits to enter the
SemaphoreSlim
.
由于您的代码是异步的,因此您应该使用 WaitAsync
,而不是 Wait
。通过在 运行 异步代码时阻塞线程,您将获得各种有趣的效果,而不是您期望的行为。例如,在 await Task.Delay(1000)
之后,您的异步工作流可能会在不同的线程上继续 运行,也可能不会继续,具体取决于您无法控制的条件。
长话短说,只需将 mSemaphore.Wait()
替换为 await mSemaphore.WaitAsync()
就可以了。