在同步块内部和外部使用字段是否安全?

Is it safe to use field inside and outside synchronized block?

背景

我们的应用程序发送在数据库中排队的电子邮件 table。我们遇到过一些重复发送电子邮件的情况,因此我正在实施锁定以防止多个线程同时发送电子邮件。

ReSharper 警告我:

the field is sometimes used inside synchronized block and sometimes used without synchronization

问题

为什么 ReSharper 告诉我这个,我为什么会担心它?

代码

这是我的(删节)代码:

private readonly IMailQueueRepository _mailQueueRepository = new MailQueueRepository();
private static object _messageQueueLock = new object();

public void SendAllQueuedMessages(IPrincipal caller)
{
    lock (_messageQueueLock) // Prevent concurrent callers
    {
        var message = _mailQueueRepository.GetUnsentMessage();
        while (message != null)
        {
            SendQueuedMessage(message);
            message = _mailQueueRepository.GetUnsentMessage();
        }
    }
}

public void SendQueuedMessage(IMessage message)
{
    // I get the ReSharper warning here on _mailQueueRepository
    var messageAttachments = _mailQueueRepository.GetMessageAttachments(message.Id);
    // etc.
}

ReSharper 无法告诉(或保证)SendQueuedMessage() 仅从同步块内调用。所以就它而言,其他代码可能会在没有同步的情况下调用 SendQueuedMessage(),并且 _mailQueueRepository 正在 SendQueuedMessage().

中使用

如果您确定没有其他代码(包含 class 的内部或外部)调用此方法,或者您已确定所有从 class 到 [=10= 的调用] 也同步 使用相同的锁对象 ,你没问题。如果您的 class 之外没有其他代码实际需要此方法,我建议您将其设为私有。

问题场景:

We've had some instances of duplicate emails being sent, so I'm implementing a lock to prevent multiple threads from sending emails simultaneously.

因此您正在使用 Lock() 来防止这种情况发生,这意味着您需要同步访问公共资源的线程,在本例中为 _mailQueueRepository

但是在相同的代码中你再次使用 _mailQueueRepository 而没有 Lock

 // I get the ReSharper warning here on _mailQueueRepository
    var messageAttachments = _mailQueueRepository.GetMessageAttachments(message.Id); // <== Accessed without a lock

所以这是一个警告,告诉您宝贵的资源以两种不同的形式被访问:一种是 synchronized(线程安全),另一种是 non-synchronized(非线程安全)。

这是一个警告,让 知道(或让你识别)这种矛盾的资源使用可能引起的问题_mailQueueRepository。您可以选择完全使用 _mailQueueRepository synchronized(与 lock 一起使用,警告将消失)或设法不 运行 竞争条件。

此外,您可能会考虑以这样一种方式重新构建代码,以便使用从 _mailQueueRepository 中提取的参数调用您的 SendQueuedMessage(),避免混合使用。