使用 SemaphoreSlim 解决竞争条件
Solving race condition using SemaphoreSlim
A race condition occurs when two threads access a shared variable at the same time. The first thread reads the variable, and the second thread reads the same value from the variable.
我在 StartAsync
中使用 SemaphoreSlim
来防止竞争条件,即两个线程可能同时更改 _clientWebSocket
。如果我们在 Timer
.
中调用 StartAsync,则可能会发生这种情况
StopAsync
呢?如果 StopAsync 没有包装在 SemaphoreSlim 中,那么两个线程通过 if
条件并调用 CloseOutputAsync 两次的机会是多少?第一个将成功关闭输出,但第二个调用将失败并返回 WebSocketException,因为 Web 套接字已关闭。考虑到我们没有改变任何变量,这是否也是一种竞争条件?考虑到我的代码的其他部分没有关闭网络套接字,它是否会发生,我真的需要包装 CloseOutputAsync 的 try/catch 吗?
我在 StopAsync 中重用了 SemaphoreSlim 以防止同时调用 StartAsync 和 StopAsync,这是个好主意吗?
private readonly SemaphoreSlim _semaphore = new(1, 1);
public async Task StartAsync(string url)
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
var ws = new ClientWebSocket();
_clientWebSocket = ws;
await _clientWebSocket.ConnectAsync(new Uri(url), CancellationToken.None).ConfigureAwait(false); // TODO: Handle
_tokenSource = new CancellationTokenSource();
_ = ReceiveLoopAsync(ws, _tokenSource.Token);
_ = SendLoopAsync(ws);
}
finally
{
_semaphore.Release();
}
}
public async Task StopAsync()
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
if (_clientWebSocket is { State: not (WebSocketState.Aborted or WebSocketState.Closed or WebSocketState.CloseSent) })
{
try
{
await _clientWebSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception) // thrown when we try to close an already closed socket
{
}
}
_clientWebSocket?.Dispose();
_clientWebSocket = null;
_tokenSource?.Cancel();
}
finally
{
_semaphore.Release();
}
}
我想说的是,如果您希望 StopAsync
被并行调用,那么这些措施是值得的。
尽管我在您的代码中发现了另一个潜在缺陷 - 您在开始和停止之间共享 _clientWebSocket
和 _tokenSource
,因此您可能会在下一种情况下结束:
- 线程 1 调用
StartAsync
并初始化 _clientWebSocket
和 _tokenSource
并存在信号量
- 线程 2 调用
StartAsync
并覆盖 _clientWebSocket
和 _tokenSource
- 线程 1
StopAsync
调用 StopAsync
将停止由 线程 2 创建的 ClientWebSocket
但是没有人会关闭由 线程 1 创建的那个
A race condition occurs when two threads access a shared variable at the same time. The first thread reads the variable, and the second thread reads the same value from the variable.
我在 StartAsync
中使用 SemaphoreSlim
来防止竞争条件,即两个线程可能同时更改 _clientWebSocket
。如果我们在 Timer
.
StopAsync
呢?如果 StopAsync 没有包装在 SemaphoreSlim 中,那么两个线程通过 if
条件并调用 CloseOutputAsync 两次的机会是多少?第一个将成功关闭输出,但第二个调用将失败并返回 WebSocketException,因为 Web 套接字已关闭。考虑到我们没有改变任何变量,这是否也是一种竞争条件?考虑到我的代码的其他部分没有关闭网络套接字,它是否会发生,我真的需要包装 CloseOutputAsync 的 try/catch 吗?
我在 StopAsync 中重用了 SemaphoreSlim 以防止同时调用 StartAsync 和 StopAsync,这是个好主意吗?
private readonly SemaphoreSlim _semaphore = new(1, 1);
public async Task StartAsync(string url)
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
var ws = new ClientWebSocket();
_clientWebSocket = ws;
await _clientWebSocket.ConnectAsync(new Uri(url), CancellationToken.None).ConfigureAwait(false); // TODO: Handle
_tokenSource = new CancellationTokenSource();
_ = ReceiveLoopAsync(ws, _tokenSource.Token);
_ = SendLoopAsync(ws);
}
finally
{
_semaphore.Release();
}
}
public async Task StopAsync()
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
if (_clientWebSocket is { State: not (WebSocketState.Aborted or WebSocketState.Closed or WebSocketState.CloseSent) })
{
try
{
await _clientWebSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception) // thrown when we try to close an already closed socket
{
}
}
_clientWebSocket?.Dispose();
_clientWebSocket = null;
_tokenSource?.Cancel();
}
finally
{
_semaphore.Release();
}
}
我想说的是,如果您希望 StopAsync
被并行调用,那么这些措施是值得的。
尽管我在您的代码中发现了另一个潜在缺陷 - 您在开始和停止之间共享 _clientWebSocket
和 _tokenSource
,因此您可能会在下一种情况下结束:
- 线程 1 调用
StartAsync
并初始化_clientWebSocket
和_tokenSource
并存在信号量 - 线程 2 调用
StartAsync
并覆盖_clientWebSocket
和_tokenSource
- 线程 1
StopAsync
调用StopAsync
将停止由 线程 2 创建的ClientWebSocket
但是没有人会关闭由 线程 1 创建的那个