views:

74

answers:

2

I have some read-only data that I want to initialise and then re-initialise periodically in a thread-safe manner. For initialisation I've pulled in Joe Duffy's LazyInit and LazyInitOnceOnly structs as detailed in his blog, which use the Double-Checked locking pattern. So my current implementation of the getter simply wraps around his LazyInitOnceOnly.Value property, with added space given to a time out check:

So the code is as follows:

public class MyData {
  public DateTime TimeStamp { get; set; }
  //actual shared data ommitted

  public MyData() { TimeStamp = DateTime.Now; }
}

public SharedDataContainer
{
  //data to be initialised thread-safe, and shared.
  //assume delegate passed on construction simply 'new's the object,
  private LazyInitOnceOnly<MyData> _sharedDataInit;
  //receives the result from the _sharedDataInit.Value property
  private MyData _sharedData;
  //time-out and reinitialise after 24 hours
  private TimeSpan _timeOut = new TimeSpan(24,0,0);

  public MyData SharedData
  {
    get{
      //slight adaptation of the use of the LazyInitOnceOnly struct - 
      //because we want to replace _sharedData later after an expiry time out.
      if(_sharedData == null)
        _sharedData = _sharedDataInit.Value;
      //need best ideas for this bit:
      if((DateTime.Now - _sharedData.TimeStamp) > _timeOut)
      {
        ReInitialise();
      }
      return _sharedData;
    }
  }
}

When the data is identified as out of date, the old data should be returned, but the new data should be prepared on a separate thread and swapped in when ready - so as not to block the caller. All subsequent reads from the data should return the old value until it's updated.

So I considered queueing new thread like this in the ReInitialise() method:

() => {
  //assume constructor pulls in all the data and sets timestamp
  _sharedData = new MyData();
}

The _sharedData overwrite in the thread will occur atomically, so that's fine. But with this code, until the rebuild is complete, all subsequent reads will try and trigger a threaded rebuild - since they are reading the old _sharedData's TimeStamp property.

What's the best way to ensure that only one rebuild is triggered?

+1  A: 

There appears to be a standard class for doing this: ReaderWriterLockSlim, or on older versions of .NET ReaderWriterLock.

ReaderWriterLockSlim appears to be a faster version of ReaderWriteLock.

This stackoverflow answer claims that the new Slim class was based on Vance Morrison's design.

Whilst you can (only very slightly) improve on his listed code for performance (by inlining his EnterMyLock, ExitMyLock and EnterMyLockSpin functions), it probably isn't worth doing so.

martinr
+1 - ReadWriterLockSlim and I are good friends and I have used it for this kind of pattern before. The reason I haven't <i>yet</i> ;) plumped for it is because I'm not protecting the whole data structure (just the initialisation and reinitialisation); and therefore using an IDisposable object that has platform handles seems a little, well, heavy.If nothing else comes up, though, I might well go this route. Thanks for your help!
Andras Zoltan
+1  A: 

Alternatively, (again not using the LazyInit stuff) set up an Int32 m_buildState = 0 in the constructor. Set a m_publishData member (in this approach this is your custom data object type not a LazyInit object type) to null.

In the getter, set d = Interlocked.CompareExchange(ref m_buildState, 1, 0). Here d is a local decision variable.

If d==2 check to see whether the data update timeout has occurred; if so, next test if Interlocked.CompareExchange(ref m_buildState, 3, 2)==2. If this is true, start a background thread to rebuild the data. Return m_publishData. (The last steps of the background rebuilding thread must be first to update m_publishData, then second to set m_buildState to 2.)

If d==3 return the m_publishData member.

If d==1 wait for d>=2. To do this optimally, wait for an event to occur (you could spin waiting/testing for d>=2 for a bit first if you want to optimise the code). Then return m_publishData.

If d==0, do a rebuild on the current thread, then set m_publishData to the data object and then set m_buildState to 2 then signal event.

I am assuming here that the time taken by the rebuild thread to rebuild is not long enough to necessitate another rebuild and that timeouts on concurrency operations are not needed. If these are not safe assumption, some more checks will be needed.

martinr
You're assumptions are right - the rebuild time should be in the region of 1-5 seconds whereas the timeout time will probably be > 1 hour.Very bare bones - like it!
Andras Zoltan
although technically the ReaderWriterLockSlim answer is correct, I like this answer as it gets around the issue of having any long-running resources outside of the state variables - I've done something similar before for a different scenario. Thanks for your help :)
Andras Zoltan