线程安全共享对象到期和重新初始化

时间:2010-01-18 12:33:35

标签: c# .net multithreading

我有一些我想要初始化的只读数据,然后以线程安全的方式定期重新初始化。对于初始化,我已经使用Double-Checked锁定模式拉入Joe Duffy的LazyInit and LazyInitOnceOnly structs as detailed in his blog。所以我当前的getter实现只是包裹了他的LazyInitOnceOnly.Value属性,并为超时检查增加了空间:

所以代码如下:

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;
    }
  }
}

当数据被识别为过期时,应返回旧数据,但新数据应在单独的线程上准备并在准备好时交换 - 以免阻塞调用者。数据的所有后续读取都应返回旧值,直到更新为止。

所以我考虑在ReInitialise()方法中排队这样的新线程:

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

线程中的_sharedData覆盖将以原子方式发生,所以没关系。但是使用此代码,在重建完成之前,所有后续读取都将尝试触发线程重建 - 因为它们正在读取旧的_sharedData的TimeStamp属性。

确保只触发一次重建的最佳方法是什么?

2 个答案:

答案 0 :(得分:1)

似乎有一个标准类来执行此操作:ReaderWriterLockSlim,或旧版本的.NET ReaderWriterLock。

ReaderWriterLockSlim似乎是ReaderWriteLock的更快版本。

This stackoverflow answer声称新的Slim课程基于Vance Morrison's design

虽然你可以(只是非常轻微地)改进他列出的性能代码(通过内联他的EnterMyLock,ExitMyLock和EnterMyLockSpin函数),但它可能不值得这样做。

答案 1 :(得分:1)

或者,(再次不使用LazyInit的东西)在构造函数中设置Int32 m_buildState = 0。将m_publishData成员(在此方法中,这是您的自定义数据对象类型而不是LazyInit对象类型)设置为null。

在getter中,设置d = Interlocked.CompareExchange(ref m_buildState,1,0)。这里d是一个局部决策变量。

如果d == 2,检查是否发生了数据更新超时;如果是这样,接下来测试Interlocked.CompareExchange(ref m_buildState,3,2)== 2。如果是这样,请启动后台线程以重建数据。返回m_publishData。 (后台重建线程的最后几步必须首先更新m_publishData,然后第二步将m_buildState设置为2.)

如果d == 3则返回m_publishData成员。

如果d == 1等待d&gt; = 2。为了做到这一点,等待事件发生(如果你想优化代码,你可以先旋转等待/测试d> = 2一点)。然后返回m_publishData。

如果d == 0,在当前线程上进行重建,然后将m_publishData设置为数据对象,然后将m_buildState设置为2,然后将信号事件设置为。

我在这里假设重建线程重建所花费的时间不足以进行另一次重建,并且不需要对并发操作进行超时。如果这些不是安全假设,则需要进行更多检查。