缓存和线程安全

时间:2011-07-29 16:04:54

标签: c# asp.net caching thread-safety

我通过System.Web.Caching.Cache-Class在ASP.NET网站中缓存数据,因为检索数据非常昂贵,并且当我们的内容人员在后端更改数据时,它只会偶尔更改一次

所以我在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 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:0)

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

请记住,随着缓存大小的增加,同步成本也会增加。您可能需要查看this帖子

通过添加私有对象来锁定,您应该能够安全地运行您的方法,以便其他线程不会干扰。

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

答案 2 :(得分:0)

除了锁定,我没有看到其他解决方案。

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

因此,性能成本不会太大。