.netmultithreadinglocking

C# - Only allow one lock to process, make all other locks wait, but then exit without executing


I have a process that multiple users may call. It's a very expensive query, but it should only ever need to be run every 5 minutes or so to refresh some archive data. I have a lock right now so that we don't run the process several times at once, which would bring the system to it's knees, but each user then has to wait for the previous locks to run before they can run. If there are 3-4 users waiting for the lock, the 4th user has to wait upwards of 20 minutes for their query to run.

What I would like to do is to lock this object and run the query on the first request. If any other requests come in, have them wait for the current lock to finish, then return without actually executing the query.

Is there anything built into .Net that can accomplish this, or do I need to write some specific code for this lock?


Solution

  • You can do this with a ManualResetEvent and a lock.

    private object _dataLock = new object();
    private ManualResetEvent _dataEvent = new ManualResetEvent(false);
    
    private ArchiveData GetData()
    {
        if (Monitor.TryEnter(_dataLock))
        {
            _dataEvent.Reset();  // makes other threads wait on the data
    
            // perform the query
    
            // then set event to say that data is available
            _dataEvent.Set();
            try
            {
                return data;
            }
            finally
            {
                Monitor.Exit(_dataLock);
            }
        }
    
        // Other threads wait on the event before returning data.
        _dataEvent.WaitOne();
        return data;
    }
    

    So the first thread to get there obtains the lock and clears the _dataEvent, indicating that other threads will have to wait for the data. There's a race condition here in that if the second client gets there before _dataEvent is reset, it will return old data. I view that as acceptable, considering that it's archive data and the window of opportunity for that to happen is pretty small.

    Other threads that come through try to obtain the lock, fail, and get blocked by the WaitOne.

    When the data is available, the thread that did the query sets the event, releases the lock, and returns the data.

    Note that I didn't put the entire lock body in a try...finally. See Eric Lippert's Locks and Exceptions do not mix for the reasons why.