C# 保证一次创建缓存项的缓存

C# 保证一次创建缓存项的缓存,c#,.net,caching,concurrency,C#,.net,Caching,Concurrency,我想创建一个缓存,这样当一个项目不存在时,只有一个请求该项目的人会花时间生成它,而其他同时请求该项目的人只会阻塞,直到第一个人缓存结果。以下是该场景的描述: 线程1进入并请求DateA的缓存数据 线程1发现它不在缓存中,并启动相对较长的 生成信息的过程 线程2进入一个窗口,看到当前正在生成信息,等待某种类型的锁 线程3也进入并看到当前正在生成信息,等待某种类型的锁 线程4进入并请求已在缓存中且根本不等待的不同密钥的数据 线程1完成生成并使用值更新缓存 线程2和线程3都会唤醒并获得现在缓存中的结

我想创建一个缓存,这样当一个项目不存在时,只有一个请求该项目的人会花时间生成它,而其他同时请求该项目的人只会阻塞,直到第一个人缓存结果。以下是该场景的描述:

  • 线程1进入并请求DateA的缓存数据
  • 线程1发现它不在缓存中,并启动相对较长的 生成信息的过程
  • 线程2进入一个窗口,看到当前正在生成信息,等待某种类型的锁
  • 线程3也进入并看到当前正在生成信息,等待某种类型的锁
  • 线程4进入并请求已在缓存中且根本不等待的不同密钥的数据
  • 线程1完成生成并使用值更新缓存
  • 线程2和线程3都会唤醒并获得现在缓存中的结果
我在想我应该在ConcurrentDictionary中拥有缓存,DateTime只存储一个日期作为键,比如
ConcurrentDictionary\u缓存

但是我看不出线程2和线程3如何能够等待一些东西。即使有另一个ConcurrentDictionary存储了某种状态标志,它们如何知道线程1何时完成


有人对如何开发这样的缓存有什么建议吗?

每次有人访问缓存对象时,都可以在缓存对象上设置一个
锁,如果缓存未命中,则执行
监视。在某个对象上输入
以指示正在创建对象,然后释放第一个锁。创建完成后,在第二个锁对象上使用
监视器。退出


缓存访问通常锁定在主对象上,但创建锁定在第二个对象上。如果希望创建并行进行,可以在字典中创建一个锁对象,其中的键与缓存的键相同。

每次有人访问缓存对象时,可以在缓存对象上放置一个
,如果缓存未命中,则执行
监视。在某个对象上输入
,以指示正在创建对象,然后释放第一个锁。创建完成后,在第二个锁对象上使用
监视器。退出


缓存访问通常锁定在主对象上,但创建锁定在第二个对象上。如果希望创建并行进行,可以在字典中创建一个锁对象,其中键与缓存的键相同。

可以使用
ConcurrentDictionary
获取对象,如下所示:

myDictionary
    .GetOrAdd(
        someDateTime,
        dt => new Lazy<CustomImmutableObject>(
            () => CreateMyCustomImmutableObject()
        )
    ).Value
myDictionary
格托拉德先生(
有时,
dt=>new Lazy(
()=>CreateMyCustomImmutableObject()
)
).价值
在(默认)线程安全模式下,
Lazy
所做的保证将确保初始化只发生一次,并且在实际实例化值之前的后续访问将被阻止

在多线程场景中,第一个访问线程安全惰性(of T)对象的Value属性的线程将初始化该对象,以便在所有线程上进行所有后续访问,并且所有线程共享相同的数据。因此,无论哪个线程初始化对象,竞争条件都是良性的

有关详细信息,请参阅

下面是我写的一个测试,以确保我没有胡言乱语。这会让你相信一切都是好的:

void Main()
{
    var now=DateTime.UtcNow;
    var d=new ConcurrentDictionary<DateTime, Lazy<CustomImmutableObject>>();
    Action f=()=>{
        var val=d
            .GetOrAdd(
                now,
                dt => new Lazy<CustomImmutableObject>(
                    () => new CustomImmutableObject()
                )
            ).Value;
        Console.WriteLine(val);
    };
    for(int i=0;i<10;++i)
    {
        (new Thread(()=>f())).Start();
    }
    Thread.Sleep(15000);
    Console.WriteLine("Finished");
}

class CustomImmutableObject
{
    public CustomImmutableObject()
    {
        Console.WriteLine("CREATING");
        Thread.Sleep(10000);
    }
}
void Main()
{
var now=DateTime.UtcNow;
var d=新的ConcurrentDictionary();
动作f=()=>{
var=d
格托拉德先生(
现在,
dt=>new Lazy(
()=>新的CustomImmutableObject()
)
).价值;
控制台写入线(val);
};
对于(int i=0;if()).Start();
}
睡眠(15000);
控制台。写入线(“完成”);
}
类CustomImmutableObject
{
公共CustomImmutableObject()
{
Console.WriteLine(“创建”);
睡眠(10000);
}
}

您可以使用
ConcurrentDictionary
获取对象,如下所示:

myDictionary
    .GetOrAdd(
        someDateTime,
        dt => new Lazy<CustomImmutableObject>(
            () => CreateMyCustomImmutableObject()
        )
    ).Value
myDictionary
格托拉德先生(
有时,
dt=>new Lazy(
()=>CreateMyCustomImmutableObject()
)
).价值
在(默认)线程安全模式下,
Lazy
所做的保证将确保初始化只发生一次,并且在实际实例化值之前的后续访问将被阻止

在多线程场景中,第一个访问线程安全惰性(of T)对象的Value属性的线程将初始化该对象,以便在所有线程上进行所有后续访问,并且所有线程共享相同的数据。因此,无论哪个线程初始化对象,竞争条件都是良性的

有关详细信息,请参阅

下面是我写的一个测试,以确保我没有胡言乱语。这会让你相信一切都是好的:

void Main()
{
    var now=DateTime.UtcNow;
    var d=new ConcurrentDictionary<DateTime, Lazy<CustomImmutableObject>>();
    Action f=()=>{
        var val=d
            .GetOrAdd(
                now,
                dt => new Lazy<CustomImmutableObject>(
                    () => new CustomImmutableObject()
                )
            ).Value;
        Console.WriteLine(val);
    };
    for(int i=0;i<10;++i)
    {
        (new Thread(()=>f())).Start();
    }
    Thread.Sleep(15000);
    Console.WriteLine("Finished");
}

class CustomImmutableObject
{
    public CustomImmutableObject()
    {
        Console.WriteLine("CREATING");
        Thread.Sleep(10000);
    }
}
void Main()
{
var now=DateTime.UtcNow;
var d=新的ConcurrentDictionary();
动作f=()=>{
var=d
格托拉德先生(
现在,
dt=>new Lazy(
()=>新的CustomImmutableObject()
)
).价值;
控制台写入线(val);
};
对于(int i=0;if()).Start();
}
睡眠(15000);
控制台。写入线(“完成”);
}
类CustomImmutableObject
{
公共CustomImmutableObject()
{
Console.WriteLine(“创建”);
睡眠(10000);
}
}

我提出了下面的方法,看起来很有效,但代价是每次读取缓存时都要锁定。假设只有一个人可以添加到cacheData,这样安全吗 一次为一个给定的密钥

static ConcurrentDictionary<DateTime, object> cacheAccess = new ConcurrentDictionary<DateTime, object>();
static ConcurrentDictionary<DateTime, int> cacheData = new ConcurrentDictionary<DateTime, int>();

static int GetValue(DateTime key)
{
    var accessLock = cacheAccess.GetOrAdd(key, x =>  new object());

    lock (accessLock)
    {
        int resultValue;
        if (!cacheData.TryGetValue(key, out resultValue))
        {
            Console.WriteLine("Generating {0}", key);
            Thread.Sleep(5000);
            resultValue = (int)DateTime.Now.Ticks;
            if (!cacheData.TryAdd(key, resultValue))
            {
                throw new InvalidOperationException("How can something else have added inside this lock?");
            }
        }

        return resultValue;
    }
}


static void Main(string[] args)
{
    var keys = new[]{ DateTime.Now.Date, DateTime.Now.Date.AddDays(-1), DateTime.Now.Date.AddDays(1), DateTime.Now.Date.AddDays(2)};
    var rand = new Random();

    Parallel.For(0, 1000, (index) =>
        {
            var key = keys[rand.Next(keys.Length)];

            var value = GetValue(key);

            Console.WriteLine("Got {0} for key {1}", value, key);
        });
}
静态ConcurrentDictionary缓存访问=新建ConcurrentDictionary();
静态ConcurrentDictionary cacheData=新ConcurrentDictionary();
静态int GetValue(日期时间键)
{