C# 具有单个实例化的全局线程安全多值自定义字典

C# 具有单个实例化的全局线程安全多值自定义字典,c#,multithreading,thread-safety,global-variables,C#,Multithreading,Thread Safety,Global Variables,我希望有一个类似于多值字典的全局对象,它在不同线程之间共享 我希望只创建一次对象(例如从数据库获取数据),然后由不同的线程使用 该对象应该可以通过附加属性(当前只有JobName和URL)轻松扩展 如果可能,我宁愿避免锁定 我面临以下问题: 下面显示的当前版本不是线程安全的 我不能使用ConcurrentDictionary,因为我已经扩展了Dictionary对象,允许每个键有多个值 这是易于修改的对象结构: public struct JobData {

我希望有一个类似于多值字典的全局对象,它在不同线程之间共享

我希望只创建一次对象(例如从数据库获取数据),然后由不同的线程使用

该对象应该可以通过附加属性(当前只有JobName和URL)轻松扩展

如果可能,我宁愿避免锁定

我面临以下问题:

  • 下面显示的当前版本不是线程安全的
  • 我不能使用ConcurrentDictionary,因为我已经扩展了Dictionary对象,允许每个键有多个值
这是易于修改的对象结构:

    public struct JobData
    {
        public string JobName;
        public string URL;
    }
我扩展了Dictionary对象,允许每个键有多个值:

    public class JobsDictionary : Dictionary<string, JobData>
    {
        public void Add(string key, string jobName, string url)
        {
            JobData data;
            data.JobName = jobName;
            data.URL = url;
            this.Add(key, data);
        }
    }
在每个线程中,我通过以下方式获得特定的作业属性:

//Get the Name
string JobName= GlobalVar.Job(jobName).JobName;

//Get the URL
string URL = string.Format((GlobalVar.Job(jobName).URL), sym);
如何创建一个“实例化”一次的自定义字典(我知道它不是正确的术语,因为它是静态的…),并且是线程安全的

谢谢

更新

好的,这是新版本

我通过删除switch语句并一次加载所有字典项简化了代码(无论如何我都需要它们)

此解决方案的优点是只锁定一次:当添加字典数据时(进入锁的第一个线程将向字典添加数据)。 当线程访问字典进行读取时,它不会被锁定

它应该是线程安全的,并且不应该导致死锁,因为jobsDictionary是私有的

public static class GlobalVar
{
    private static JobsDictionary jobsDictionary = new JobsDictionary();   
    public static JobData Job(string jobCat)
    {
        JobData result;
        if (jobsDictionary.TryGetValue(jobCat, out result))
            return result;

        //if the jobsDictionary is not initialized yet...
        lock (jobsDictionary)
        {
            if (jobsDictionary.Count == 0)
            {
                //TODO: get the Data from the Database
                jobsDictionary.Add("earnings", "EarningsWhispers", "http://www.earningswhispers.com/stocks.asp?symbol={0}");
                jobsDictionary.Add("stock", "YahooStock", "http://finance.yahoo.com/q?s={0}");   
                jobsDictionary.Add("functions", "Functions", null);  
            }
            return jobsDictionary[jobCat];
        }
    }
}

如果只填充一次集合,则根本不需要任何锁定,因为字典在仅从中读取时是线程安全的。如果要防止多个线程多次初始化,可以在初始化期间使用双重检查锁,如下所示:

static readonly object syncRoot = new object();
static Dictionary<string, JobData> cache;

static void Initialize()
{
    if (cache == null)
    {
        lock (syncRoot)
        {
            if (cache == null)
            {
                cache = LoadFromDatabase();
            }
        }
    }
}
public object GetValue(object key)
{
    object result;
    if(_dictionary.TryGetValue(key, out result) 
        return result;

     lock(_dictionary)
     {
        if(_dictionary.TryGetValue(key, out result) 
            return result;
        //some get data code
        _dictionary[key]=result;
        return result;
     }
}
您还可以使用静态构造函数,这将防止您自己编写双重检查锁。但是,在静态构造函数中调用数据库是危险的,因为静态构造函数只运行一次,当它失败时,整个类型将在AppDomain存在的时间内不可用。换句话说,发生这种情况时,必须重新启动应用程序

更新2:

您还可以使用.NET 4.0的惰性,它比双重检查锁更安全,因为它更容易实现(也更容易正确实现),并且在内存型号较弱的处理器体系结构上(比ARM等x86更弱)也是线程安全的:

静态延迟缓存=
新的惰性(()=>LoadFromDatabase());
1)使用singleton Patrn创建一个实例(其中一种方法是像您所做的那样使用
静态
类)

2) 要使任何线程安全,您应该使用
lock
或其模拟功能。如果您担心不必要的锁,请执行以下操作:

static readonly object syncRoot = new object();
static Dictionary<string, JobData> cache;

static void Initialize()
{
    if (cache == null)
    {
        lock (syncRoot)
        {
            if (cache == null)
            {
                cache = LoadFromDatabase();
            }
        }
    }
}
public object GetValue(object key)
{
    object result;
    if(_dictionary.TryGetValue(key, out result) 
        return result;

     lock(_dictionary)
     {
        if(_dictionary.TryGetValue(key, out result) 
            return result;
        //some get data code
        _dictionary[key]=result;
        return result;
     }
}

为什么要阻止锁定?你测试过速度了吗?它对你的应用程序来说太慢了,还是你只是在猜测?只是在猜测是否有一种不锁定的好方法。。。毕竟,通过DB创建的对象在开始时只应执行一次。如果没有,那么我将使用锁定版本同一密钥的多个值-这是一个
查找
谢谢。根据收到的答案,我找到了一个很好的解决方案。我将稍后发布,因为我的名声不好,我不得不等待8个小时。@ili:虽然你的示例看起来像是一个双重检查锁,但它不是。事实上,您的示例不是线程安全的!您正在锁外阅读词典,如果词典可以同时更改,这是不安全的,因为锁内有写操作。@史蒂文:我已经用类似于ili的解决方案进行了测试,我必须说它工作得很好。我已经用几十个线程对它进行了多次测试,它总是正常工作。在此之前,我经常遇到jobsDictionary中缺少数据的问题。请稍候,我稍后再发,然后你可以给我你的反馈。Cheers@Forma:它在测试中正常工作的事实并不意味着代码中没有并发错误。然而,事实是,该类明确声明它支持多个读卡器,“只要集合未被修改”。因此,即使您可以证明此解决方案目前是线程安全的(仅通过运行几个并发线程是无法做到的),该类的未来版本也可能会在代码中引入竞争条件,因为合同明确规定你不应该依赖它来保证安全。@Steven:在我的新版本中,字典只修改一次——当它是由第一个通过锁的线程生成的时候。所以,要么字典不包含数据,要么如果它包含数据,它将不再被修改。谢谢。根据收到的答案,我找到了一个很好的解决方案。由于我的名声不好,我不得不等8个小时,所以我会稍后再发。
static Lazy<Dictionary<string, JobData>> cache =
    new Lazy<Dictionary<string, JobData>>(() => LoadFromDatabase());
public object GetValue(object key)
{
    object result;
    if(_dictionary.TryGetValue(key, out result) 
        return result;

     lock(_dictionary)
     {
        if(_dictionary.TryGetValue(key, out result) 
            return result;
        //some get data code
        _dictionary[key]=result;
        return result;
     }
}