监控TryEnter逻辑

时间:2015-09-29 09:22:55

标签: c# thread-safety

我有一段代码我只想执行一次,但可以同时获得多个请求,更具体地说,是文档操作。

逻辑是第一个到达的线程代码和其他人等待第一个完成然后全部返回。

由于这可能发生在任何文档中,我创建了一个字典,其中包含每个文档的锁定对象(通过id)

private static readonly object LockFetchDocument = new object();
private static readonly Dictionary<int, object> DocumentLocks = new Dictionary<int, object>();

//Let's add the monitor for this document (this has to be thread-safe too)
lock (LockFetchDocument)
{
    if (!DocumentLocks.ContainsKey(docModel.DocumentId))
    {
        DocumentLocks.Add(docModel.DocumentId, new object());
    }
}

//let's aquire the Monitor for this document so we don't prefetch the same document at the same time
if (!Monitor.TryEnter(DocumentLocks[docModel.DocumentId]))
{
    //document is being exported, let's wait for it to end
    lock (DocumentLocks[docModel.DocumentId])
    {
        //document was exported, return
        return;
    }
}

//Monitor aquired
try
{
    //DO WORK
}
finally
{
    //Let's release the monitor for this document
    lock (DocumentLocks[docModel.DocumentId])
    {
        Monitor.Exit(DocumentLocks[docModel.DocumentId]);
    }
}

这个逻辑好吗?为了使字典添加,我使用另一个储物柜。但我得到一个Resharper警告,Monitor.TryEnter不在同步块内。需要吗?

2 个答案:

答案 0 :(得分:2)

我会略微重建它:

var lockTaken = false;
object gate = null;

//Let's add the monitor for this document (this has to be thread-safe too)
lock (LockFetchDocument)
{
    // acquire the gate only once from the dictionary.
    if (!DocumentLocks.TryGetValue(docModel.DocumentId, out gate))
    {
        gate = new object();
        DocumentLocks.Add(docModel.DocumentId, gate);
    }
}

try
{
    //let's aquire the Monitor for this document so we don't prefetch the same document at the same time
    Monitor.TryEnter(gate, ref lockTaken);

    if (!lockTaken)
    {
        //document is being exported, let's wait for it to end
        lock (gate)
        {
            //document was exported, return
            return;
        }
    }

    //Monitor aquired
    //DO WORK
}
finally
{
    //Let's release the monitor for this document
    if (lockTaken)
    {
        Monitor.Exit(gate);
    }
}

通过使用这种方法,您永远不会遇到通过未释放的监视器进行死锁的问题。最后一个块中的双锁也已经消失了。最后但并非最不重要的是,对字典的多次调用已经消失,如果有人在你奔跑的时候操纵它,它会很强大。

答案 1 :(得分:2)

您的整体想法很好,但您的代码存在一些问题:

  • 您不应该使用System.Threading.Monitorlock()同时锁定同一个对象两次。
  • 在第一部分中,您在使用DocumentLocks时正确锁定,但稍后在没有锁定的情况下访问它。

    private static readonly Dictionary<int, object> DocumentLocks = new Dictionary<int, object>();
    private static readonly object LockFetchDocument = new object();
    
    public static void ExportDocument(int ID)
    {
        object DocumentLocker;
        lock (LockFetchDocument)
        {
            // Only access DocumentLocks inside this block
            if (DocumentLocks.ContainsKey(ID))
            {
                DocumentLocker = DocumentLocks[ID];
            }
            else
            {
                DocumentLocker = new object();
                DocumentLocks[ID] = DocumentLocker;
            }
        }
    
        bool lockTaken = false;
        try
        {
            System.Threading.Monitor.TryEnter(DocumentLocker, ref lockTaken);
            if (!lockTaken)
            {
                // Export is already running, wait for it to finish then return
                System.Threading.Monitor.Enter(DocumentLocker, ref lockTaken);
                // When we return the finally block will still be executed and release the lock
                return;
            }
    
            // Do stuff
        }
        finally
        {
            if (lockTaken)
                System.Threading.Monitor.Exit(DocumentLocker);
        }
    }