【问题标题】:thread-safe loading of a static collection静态集合的线程安全加载
【发布时间】:2013-12-15 17:05:29
【问题描述】:

我有一些静态 Dictionary 对象,其中包含一些常量列表,因此我不必在每次加载网站时都从数据库加载它们(例如:国家列表、类别列表)。

所以我有一个静态函数来检查实例是否为空,如果是查询数据库,则实例化静态变量,然后用数据填充它。

由于它是一个网站,可能会出现这样一种情况,即当对象为空时,不止一个人试图同时访问该信息,并且所有这样做的人都会同时调用该过程(即真的没有必要,导致对数据库的不需要的查询,并可能导致列表中的重复对象)。

我知道有一种方法可以使这种加载线程安全(只是不太确定如何) - 有人能指出我正确的方向吗?我应该使用锁吗?

谢谢

更新二:

这是我写的(这是一个好的线程安全代码吗?)

private static Lazy<List<ICountry>> _countries  = new Lazy<List<ICountry>>(loadCountries);

private static List<ICountry> loadCountries()
{
        List<ICountry> result = new List<ICountry>();

        DataTable dtCountries = SqlHelper.ExecuteDataTable("stp_Data_Countries_Get");
        foreach (DataRow dr in dtCountries.Rows)
        {
            result.Add(new Country
                {
                    ID = Convert.ToInt32(dr["CountryId"]),
                    Name = dr["Name"].ToString()
                });
        }

        return result;
}

public static List<ICountry> GetAllCountries()
{
    return _countries.Value;
}

【问题讨论】:

  • 关于您的更新:如果延迟加载代码抛出,异常将由 Lazy 对象存储并始终重新抛出。然后,应用程序将被冲洗,直到它重新启动。

标签: c# asp.net thread-safety


【解决方案1】:

您可以使用Lazy 以惰性和线程安全的方式加载资源:

Lazy<List<string>> countries = 
    new Lazy<List<string>>(()=> /* get your countries from db */);

更新:

public static class HelperTables
{
   private static Lazy<List<ICountry>> _countries;

   static HelperTables //Static constructor
   {
       //Instantiating the lazy object in the static constructor will prevent race conditions
      _countries = new Lazy<List<ICountry>>(() =>
      {
        List<ICountry> result = new List<ICountry>();

        DataTable dtCountries = SqlHelper.ExecuteDataTable("stp_Data_Countries_Get");
        foreach (DataRow dr in dtCountries.Rows)
        {
            result.Add(new Country
            {
                ID = Convert.ToInt32(dr["CountryId"]),
                Name = dr["Name"].ToString()
            });
        }

        return result;
      });
   }

   public static List<ICountry> GetAllCountries()
   {
      return _countries.Value;
   }
}

【讨论】:

  • 他们写道:“使 Lazy 对象线程安全并不能保护延迟初始化的对象。如果多个线程可以访问延迟初始化的对象,则必须使其属性和方法对多线程访问安全。”你能解释一下吗?
  • @developer82 这意味着您必须自己处理Lazy&lt;T&gt;返回的对象的线程安全。在这种情况下,例如,返回的 List&lt;string&gt; 实例不是线程安全的,只有它的构造和初始化受 Lazy 保护。
  • @Alberto 从页面中的示例中我看到他们确实使用了 lock 关键字 - 那么这是否意味着在任何情况下我都需要使用该关键字我只希望一个线程初始化它?
  • @developer82 否。在示例中,锁用于同步对对象(LargeObject)的访问之后它被Lazy&lt;T&gt;初始化。
  • 嗨,请查看我上面的更新 - 这是一个好的线程安全代码吗?还是我不在这儿?谢谢:)
【解决方案2】:

如果您使用的是 .NET 4.0,则可以使用内置的 Lazy 泛型类。

private static Lazy<YourObject> data = new Lazy<YourObject>(YourInitializationFunction);
public static YourObject Data { get { return data.Value; } }

请注意,您必须在定义 this 的类中添加一个静态构造函数,否则它不是完全线程安全的。

如果您不在 .NET 4.0+ 上,您可以编写自己的代码。基本模式如下所示:

private static YourObject data;
private static object syncObject = new object();

public static YourObject Data
{
  get
    {
        if (data == null)
        {
            lock (syncObject)
            {
                if (data != null)
                    return data;

                var obj = new YourObject();

                return (YourObject)Interlocked.Exchange(ref data, obj);
            }
        }

        return data;
    }
}

【讨论】:

  • @BRAHIMKamel:是的。线程安全模式的默认值是ExecutionAndPublication,其实就是使用显式锁的版本。
  • 好的,但不是你应该向集合添加新值的地方
  • @BRAHIMKamel:是的,但这不是问题的一部分。 OP 要求一种从数据库中安全地初始化常量列表的方法。他可以通过任何他想要的方式实现 YourInitializationFunction 来做到这一点。在安全加载数据后对其进行操作是完全不同的事情,而不是 OP 所要求的。
  • 嗨,请查看我上面的更新 - 这是一个好的线程安全代码吗?还是我不在这儿?谢谢:)
  • 不,不是这样。您必须将构造函数添加到字段声明中。然后在您的 Get* 方法中,您只返回 country.Value,它会为您处理线程安全初始化 :)
猜你喜欢
  • 2011-12-26
  • 2023-04-07
  • 2014-09-16
  • 2012-09-22
  • 2015-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多