У меня есть некоторые данные только для чтения, которые я хочу инициализировать, а затем повторно инициализировать периодически поточно-безопасным способом. Для инициализации я вытащил Joe Duffy структуры LazyInit и LazyInitOnceOnly, подробно описанные в его блоге, в которых используется шаблон блокировки с двойной проверкой. Таким образом, моя текущая реализация 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 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.
Какой лучший способ обеспечить запуск только одного восстановления?
В качестве альтернативы (опять же не используя материал LazyInit) в конструкторе задается Int32 m_buildState = 0. Установите член m_publishData (в этом подходе это ваш тип данных пользовательских данных, а не тип объекта LazyInit) до нуля.
В 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, затем передайте событие.
Я предполагаю, что время, затрачиваемое на перестроение для восстановления, недостаточно для того, чтобы потребовать другую перестройку и что тайм-ауты в операциях concurrency не нужны. Если это не безопасное предположение, потребуется еще несколько проверок.
Для этого обычно существует стандартный класс: ReaderWriterLockSlim или более ранние версии .NET ReaderWriterLock.
ReaderWriterLockSlim, по-видимому, является более быстрой версией ReaderWriteLock.
qaru.site/questions/1704241/... утверждает, что новый класс Slim был основан на Vance Morrison design.
В то время как вы можете (только очень немного) улучшить свой код для повышения производительности (путем вложения его функций EnterMyLock, ExitMyLock и EnterMyLockSpin), это, вероятно, не стоит делать.