Кеширование и безопасность потоков - PullRequest
3 голосов
/ 29 июля 2011

Я кеширую данные на веб-сайте ASP.NET через System.Web.Caching.Cache-Class, потому что получение данных очень дорого, и оно меняется только время от времени, когда наши сотрудники изменяют данные в бэкэнде..

Поэтому я создаю данные в Application_Start и сохраняю их в Cache со сроком действия 1 день.

При доступе к данным (происходит на многих страницах веб-сайта), у меня естьчто-то вроде этого сейчас в статическом классе CachedData:

public static List<Kategorie> GetKategorieTitelListe(Cache appCache)
{
    // get Data out of Cache
    List<Kategorie> katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;
    // Cache expired, retrieve and store again
    if (katList == null)
    {
            katList = DataTools.BuildKategorienTitelListe();
            appCache.Insert(CachedData.NaviDataKey, katList, null, DateTime.Now.AddDays(1d), Cache.NoSlidingExpiration);
    }
    return katList;
}

Проблема, которую я вижу с этим кодом, заключается в том, что он не является потокобезопасным.Если два пользователя открывают две из этих страниц одновременно, а кэш-память только что закончилась, существует риск, что данные будут извлечены несколько раз.

Но если я заблокирую тело метода, я столкнусь с производительностьюпроблемы, потому что только один пользователь одновременно может получить список данных.

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

Ответы [ 3 ]

4 голосов
/ 29 июля 2011

Вы правы, ваш код не безопасен для потоков.

// this must be class level variable!!!
private static readonly object locker = new object();

    public static List<Kategorie> GetKategorieTitelListe(Cache appCache)
    {
        // get Data out of Cache
        List<Kategorie> katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;

        // Cache expired, retrieve and store again
        if (katList == null)
        {
            lock (locker)
            {
                katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;

                if (katlist == null)  // make sure that waiting thread is not executing second time
                {
                    katList = DataTools.BuildKategorienTitelListe();
                    appCache.Insert(CachedData.NaviDataKey, katList, null, DateTime.Now.AddDays(1d), Cache.NoSlidingExpiration);
                }
            }
        }
        return katList;
    }
0 голосов
/ 29 июля 2011

Я не вижу другого решения, кроме блокировки.

private static readonly object _locker = new object ();

public static List<Kategorie> GetKategorieTitelListe(Cache appCache)
{
    List<Kategorie> katList;

    lock (_locker)
    {
        // get Data out of Cache
        katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;
        // Cache expired, retrieve and store again
        if (katList == null)
        {
                katList = DataTools.BuildKategorienTitelListe();
                appCache.Insert(CachedData.NaviDataKey, katList, null, DateTime.Now.AddDays(1d), Cache.NoSlidingExpiration);
        }
    }
    return katList;
}

Как только данные находятся в кеше, параллельные потоки будут только ждать время вывода данных, т.е. эта строка кода:

katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;

Так что стоимость исполнения не будет драматичной.

0 голосов
/ 29 июля 2011

В документации MSDN указано, что класс ASP.NET Cache является поточно-ориентированным, что означает, что их содержимое свободно доступно любому потоку в домене приложения (например, чтение / запись будет атомарным).

Просто имейте в виду, что с ростом размера кэша увеличивается стоимость синхронизации.Возможно, вы захотите взглянуть на эту запись

Добавив закрытый объект для блокировки, вы сможете безопасно запустить свой метод, чтобы другие потоки не мешали.

private static readonly myLockObject = new object();

public static List<Kategorie> GetKategorieTitelListe(Cache appCache)
{
    // get Data out of Cache
    List<Kategorie> katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;

    lock (myLockObject)
    {
        // Cache expired, retrieve and store again
        if (katList == null)
        {
            katList = DataTools.BuildKategorienTitelListe();
            appCache.Insert(CachedData.NaviDataKey, katList, null, DateTime.Now.AddDays(1d), Cache.NoSlidingExpiration);
        }
        return katList;
    }
}
...