缓存和线程安全

本文关键字:安全 线程 缓存 | 更新日期: 2023-09-27 18:04:10

我在一个ASP缓存数据。. NET网站通过System.Web.Caching。缓存类,因为检索数据的成本非常高,而且只有当我们的内容人员在后端更改数据时,它才会偶尔更改。

所以我在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;
}

我看到这个代码的问题是,它不是线程安全的。如果两个用户同时打开其中两个页面,并且缓存刚刚用完,则存在多次检索数据的风险。

但是如果我锁定方法体,就会遇到性能问题,因为一次只有一个用户可以获得数据列表。

是否有简单的方法来防止这种情况?这种情况下最好的做法是什么?

缓存和线程安全

你是对的,你的代码不是线程安全的。

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

MSDN文档声明ASP。. NET缓存类是线程安全的——这意味着它们的内容可以被AppDomain中的任何线程自由访问(例如,读/写将是原子的)。

请记住,随着缓存大小的增长,同步成本也会增加。你可能想看看这篇文章

通过添加一个私有对象来锁定,你应该能够安全地运行你的方法,这样其他线程就不会干扰。

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

我看不出除了锁以外还有什么办法。

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

所以性能成本不会太大