2010-01-18 38 views
2

我有一些只读数据,我想要初始化,然后以线程安全方式定期重新初始化。对于初始化,我拉入了Joe Duffy的LazyInit and LazyInitOnceOnly structs as detailed in his blog,它使用了Double-Checked锁定模式。所以我目前执行的吸气剂只是环绕他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属性。

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

+0

所以你希望重建发生在一个新的,衍生出来的后台工作线程和主客户线程上,以便直接返回**旧**数据(即使getter知道它是旧的)直到在后台线程上重建做完了??只是检查这里的要求.... – martinr 2010-01-18 12:45:39

+0

是的,没错 - 对不起,我在我的问题中有点松了一口气!这是我的一个诅咒。 – 2010-01-18 12:51:44

+0

已更新文本,使其更清晰一点 - 谢谢;) – 2010-01-18 12:55:42

回答

1

或者,(再次不使用LazyInit的东西)在构造函数中设置Int32 m_buildState = 0。将m_publishData成员设置为null(在此方法中,这是您的自定义数据对象类型而不是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> = 2。要做到这一点,请等待事件发生(如果要优化代码,可以先等待/测试d> = 2)。然后返回m_publishData。

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

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

+0

你的假设是正确的 - 重建时间应该在1-5秒的范围内,而超时时间可能会超过1小时。 非常光秃秃的骨头 - 就像它! – 2010-01-18 17:06:35

+0

虽然在技术上ReaderWriterLockSlim答案是正确的,但我喜欢这个答案,因为它解决了在状态变量之外有任何长期运行的资源的问题 - 我之前为其他场景做过类似的事情。谢谢你的帮助 :) – 2010-01-19 16:39:44

1

看起来有这样一个标准的类:ReaderWriterLockSlim,或者在旧版本的.NET ReaderWriterLock上。

ReaderWriterLockSlim看起来是ReaderWriteLock的更快版本。

This stackoverflow answer声称新的Slim类是基于Vance Morrison's design

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

+0

+1 - ReadWriterLockSlim和我是好朋友,我以前用它来做这种模式。我之所以没有还有;)是因为我没有保护整个数据结构(仅仅是初始化和重新初始化)。因此使用具有平台手柄的IDisposable对象似乎有点沉重。 如果没有其他的东西出现,我可能会走这条路。谢谢你的帮助! – 2010-01-18 15:26:57