更新
请参阅下面的更新,当您安装.Net 4.6时,此问题已解决。
我想在UpdateCallback
的CacheItemPolicy
中实现某些东西。
如果这样做并测试我的代码在同一个缓存实例(MemoryCache.Default
)上运行多个线程,则在调用cache.Set
方法时遇到以下异常。
System.Runtime.Caching.dll!System.Runtime.Caching.MemoryCacheEntry.RemoveDependent(System.Runtime.Caching.MemoryCacheEntryChangeMonitor dependent = {unknown}) C#
System.Runtime.Caching.dll!System.Runtime.Caching.MemoryCacheEntryChangeMonitor.Dispose(bool disposing = {unknown}) C#
System.Runtime.Caching.dll!System.Runtime.Caching.ChangeMonitor.DisposeHelper() C#
System.Runtime.Caching.dll!System.Runtime.Caching.ChangeMonitor.Dispose() C#
System.Runtime.Caching.dll!System.Runtime.Caching.ChangeMonitor.InitializationComplete() C#
System.Runtime.Caching.dll!System.Runtime.Caching.MemoryCacheEntryChangeMonitor.InitDisposableMembers(System.Runtime.Caching.MemoryCache cache = {unknown}) C#
System.Runtime.Caching.dll!System.Runtime.Caching.MemoryCacheEntryChangeMonitor..ctor(System.Collections.ObjectModel.ReadOnlyCollection<string> keys = {unknown}, string regionName = {unknown}, System.Runtime.Caching.MemoryCache cache = {unknown}) C#
System.Runtime.Caching.dll!System.Runtime.Caching.MemoryCache.CreateCacheEntryChangeMonitor(System.Collections.Generic.IEnumerable<string> keys = {unknown}, string regionName = {unknown}) C#
System.Runtime.Caching.dll!System.Runtime.Caching.MemoryCache.Set(string key = {unknown}, object value = {unknown}, System.Collections.ObjectModel.Collection<System.Runtime.Caching.ChangeMonitor> changeMonitors = {unknown}, System.DateTimeOffset absoluteExpiration = {unknown}, System.TimeSpan slidingExpiration = {unknown}, System.Runtime.Caching.CacheEntryUpdateCallback onUpdateCallback = {unknown}) C#
System.Runtime.Caching.dll!System.Runtime.Caching.MemoryCache.Set(string key = {unknown}, object value = {unknown}, System.Runtime.Caching.CacheItemPolicy policy = {unknown}, string regionName = {unknown}) C#
我知道
MemoryCache
是线程安全的,所以我没有想到任何问题。更重要的是,如果我不指定UpdateCallback
,那么一切都很好!好的,为了重现此行为,我们在这里使用了一些控制台应用程序:
这段代码只是我为另一个库所做的某些测试的简化版本。它旨在在多线程环境中引起冲突,例如得到一个条件,其中一个线程尝试读取键/值,而另一个线程已将其删除,等等。
同样,这一切都应该正常工作,因为MemoryCache是线程安全的(但事实并非如此)。
class Program
{
static void Main(string[] args)
{
var threads = new List<Thread>();
foreach (Action action in Enumerable.Repeat<Action>(() => TestRun(), 10))
{
threads.Add(new Thread(new ThreadStart(action)));
}
threads.ForEach(p => p.Start());
threads.ForEach(p => p.Join());
Console.WriteLine("done");
Console.Read();
}
public static void TestRun()
{
var cache = new Cache("Cache");
var numItems = 200;
while (true)
{
try
{
for (int i = 0; i < numItems; i++)
{
cache.Put("key" + i, new byte[1024]);
}
for (int i = 0; i < numItems; i++)
{
var item = cache.Get("key" + i);
}
for (int i = 0; i < numItems; i++)
{
cache.Remove("key" + i);
}
Console.WriteLine("One iteration finished");
Thread.Sleep(0);
}
catch
{
throw;
}
}
}
}
public class Cache
{
private MemoryCache CacheRef = MemoryCache.Default;
private string InstanceKey = Guid.NewGuid().ToString();
public string Name { get; private set; }
public Cache(string name)
{
Name = name;
}
public void Put(string key, object value)
{
var policy = new CacheItemPolicy()
{
Priority = CacheItemPriority.Default,
SlidingExpiration = TimeSpan.FromMinutes(1),
UpdateCallback = new CacheEntryUpdateCallback(UpdateCallback)
};
MemoryCache.Default.Set(key, value, policy);
}
public static void UpdateCallback(CacheEntryUpdateArguments args)
{
}
public object Get(string key)
{
return MemoryCache.Default[ key];
}
public void Remove(string key)
{
MemoryCache.Default.Remove( key);
}
}
如果运行该异常,则应直接获取该异常。如果注释掉UpdateCallback设置程序,则不应再出现异常。另外,如果仅运行一个线程(将
Enumerable.Repeat<Action>(() => TestRun(), 10)
更改为, 1)
),它将正常工作。到目前为止,我发现了什么:
我发现,无论何时设置
Update
或Remove
回调,MemoryCache
都会使用OnUpdateSentinel<your key>
这样的键为您创建一个附加的哨兵缓存条目。似乎它还在该项目上创建了一个更改监视器,因为对于滑动过期,只有此哨兵项目才会获得超时设置!如果该项目过期,则将调用回调。我最好的猜测是,如果您尝试在大致相同的时间使用相同的键/策略/回调创建相同的项目,那么如果我们定义了回调,那么
MemoryCache
中就会出现问题...同样从堆栈跟踪中可以看到,该错误出现在ChangeMonitor的
Dispose
方法内的某个位置。我没有在CacheItemPolicy
中添加任何更改监视器,因此似乎是内部控制的内容...如果这是正确的,则可能这是MemoryCache中的错误。我通常不敢相信在这些库中找到错误,因为通常这是我的错:p,也许我太愚蠢而无法正确地实现此目的...因此,任何帮助或提示都将不胜感激;)
2014年8月更新:
似乎他们尝试修复this issue。
2015年5月更新:
如果您安装例如NET 4.6随附的VS 2015 RC。我无法真正验证.Net的版本是否可以修复该问题,因为现在它可以在该项目使用的所有版本中正常工作。我将其设置为.Net 4.5、4.5.1或4.5.2都没关系,该错误不再可重现。
最佳答案
微软似乎已修复此问题,至少在.Net 4.5.2中已修复。浏览referencesource.microsoft.com显示,现在他们用来存储内部数据的字典的访问权限周围有一个锁:
MemoryCacheEntry.cs
internal void RemoveDependent(MemoryCacheEntryChangeMonitor dependent) {
lock (this) {
if (_fields._dependents != null) {
_fields._dependents.Remove(dependent);
}
}
}
关于c# - 为什么MemoryCache抛出NullReferenceException,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21680429/