Потокобезопасный общий срок действия и повторная инициализация общего объекта

2

У меня есть некоторые данные только для чтения, которые я хочу инициализировать, а затем повторно инициализировать периодически поточно-безопасным способом. Для инициализации я вытащил 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.

Какой лучший способ обеспечить запуск только одного восстановления?

  • 0
    Таким образом, вы хотите, чтобы перестройка происходила в новом порожденном фоновом рабочем потоке, а основной клиентский поток немедленно возвращался со старыми данными (даже если получатель знает, что он старый) до тех пор, пока перестройка в фоновом потоке не будет завершена. Просто проверяю требования здесь ....
  • 0
    Да, правильно - извините, я немного колебался в своем вопросе! Это мое проклятие.
Показать ещё 1 комментарий
Теги:
multithreading

2 ответа

1
Лучший ответ

В качестве альтернативы (опять же не используя материал 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 не нужны. Если это не безопасное предположение, потребуется еще несколько проверок.

  • 0
    Ваши предположения верны - время восстановления должно составлять 1-5 секунд, тогда как время ожидания, вероятно, будет> 1 часа. Очень голые кости - нравится!
  • 0
    хотя технически ответ ReaderWriterLockSlim является правильным, мне нравится этот ответ, поскольку он решает проблему наличия каких-либо долговременных ресурсов вне переменных состояния - раньше я делал нечто подобное для другого сценария. Спасибо за вашу помощь :)
1

Для этого обычно существует стандартный класс: ReaderWriterLockSlim или более ранние версии .NET ReaderWriterLock.

ReaderWriterLockSlim, по-видимому, является более быстрой версией ReaderWriteLock.

qaru.site/questions/1704241/... утверждает, что новый класс Slim был основан на Vance Morrison design.

В то время как вы можете (только очень немного) улучшить свой код для повышения производительности (путем вложения его функций EnterMyLock, ExitMyLock и EnterMyLockSpin), это, вероятно, не стоит делать.

  • 0
    +1 - ReadWriterLockSlim и я хорошие друзья, и я использовал его для такого рода паттернов раньше. Причина, по которой я не <i> еще </ i>;) это объясняется тем, что я не защищаю всю структуру данных (только инициализацию и повторную инициализацию); и поэтому использование объекта IDisposable, имеющего дескрипторы платформы, кажется немного тяжелым. Если больше ничего не подойдет, я вполне могу пойти по этому пути. Спасибо за вашу помощь!

Ещё вопросы

Сообщество Overcoder
Наверх
Меню