我有一个使用lock
的类来提供对私有字段的线程安全访问。但是,由于下面详述的原因,我正在考虑切换到使用SemaphoreSlim
来实现线程安全性。我知道如果我用lock (_lock)
(我现在这样做)围绕对该字段的所有访问,我保证对该字段的写入将是原子和,所有线程都会看到最近写的价值(source)。我想知道我是否用SemaphoreSlim
获得这两种保证,或者只是保证原子写入。
我有一个接口,我写了一段时间来代表可以用来与外部设备通信的服务:
public interface ICommunicationService : IDisposable
{
event EventHandler<ReceivedMessageEventArgs> MessageReceived;
void SendMessage(GenericMessage message);
void Start();
}
我最近遇到过这样一种情况,我更喜欢这些方法的异步实现,显然当你开始做异步时,你需要用它来整理它以避免死锁等,所以我打算更新界面和我为支持异步操作而编写的所有现有实现:
public interface ICommunicationService : IDisposable
{
event EventHandler<ReceivedMessageEventArgs> MessageReceived;
Task SendMessage(GenericMessage message);
Task Start();
}
我的一个实现此接口的类在其SendMessage()
和Start()
的实现中执行了一系列锁定:
public virtual void SendMessage(GenericMessage message)
{
CheckDisposed();
lock (_lock)
{
if (CommunicationState != CommunicationState.Up)
{
throw new InvalidOperationException("Message cannot be sent as communication is not established.");
}
try
{
_currentDelegate.SendMessage(message);
}
catch (Exception)
{
TerminateCommunication();
}
}
}
尝试将此代码切换为异步实现,我注意到我无法在lock
语句中等待任务。根据异步大师Stephen Cleary的blog post on the subject,执行此操作的异步方式是使用SemaphoreSlim
。
根据该帖子,我已将SendMessage
实施更改为:
public virtual async Task SendMessage(GenericMessage message)
{
CheckDisposed();
if (CommunicationState != CommunicationState.Up)
{
throw new InvalidOperationException("Message cannot be sent as communication is not established.");
}
// _mutex = new SemaphoreSlim(0, 1)
await _mutex.WaitAsync().ConfigureAwait(false);
try
{
await _currentDelegate.SendMessage(message);
}
catch (Exception)
{
TerminateCommunication();
}
finally
{
_mutex.Release();
}
}
我想知道的是,我是否保证任何给定的线程在执行_currentDelegate
时会看到await _currentDelegate.SendMessage(message)
的最新值,或者我是否需要使用其他构造来确保其他线程立即可以看到写入。
特别是,这个类有另一种方法TryRestartCommunication
:
private bool TryRestartCommunication(bool initialStart)
{
// ...
lock (_lock)
{
// ...
try
{
_currentDelegate = _factory();
_currentDelegate.Start();
_currentDelegate.MessageReceived += MessageReceived;
CommunicationState = CommunicationState.Up;
return true;
}
// ...
}
}
如果我重新实现此方法来锁定信号量并且有一些线程A调用TryRestartCommunication()
并且在线程B调用SendMessage()
之后立即执行,我保证线程B将看到新的值线程A设置_currentDelegate
?
如果没有,那么一个合适的解决方案是让_currentDelegate
易变,还是使用Interlocked
更新其值?
修改
得到了密切的投票,因为显然这个问题不够明确。让我尝试尽可能清楚:如果我从使用lock
保护我的关键区域切换到SemaphoreSlim
,我是否需要将共享字段标记为volatile
(或者某些内容)类似的)确保相同级别的线程安全性?
答案 0 :(得分:2)
如果您通过lock()保护关键区域/数据切换到SemaphoreSlim(1),您将获得相同级别的线程安全性。
我们已经多次完成,并且从未遇到过单一数据保护错误。
但是,请注意,在await xxx.ConfigureAwait(false);
之后,您可能会在不同的同步上下文(线程)中结束,并且方法await _currentDelegate.SendMessage(message);
可能无法正常使用它。例如,如果该方法访问Webform UI控件。