更新
请看下面的更新,只要安装了.NET 4.6,问题就解决了。
我想在CacheItemPolicy
的UpdateCallback
中实现一些东西。
如果我这样做,并在同一个缓存实例(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
,一切都运行得很好!
好的,为了重现这个问题,我们来看一些控制台应用程序代码: 这段代码只是另一个库的一些测试的简化版本。它旨在在多线程环境下引起冲突,例如当一个线程尝试读取Key/Value时,另一个线程已经删除它等等...
再次强调,这应该都能正常工作,因为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 中的 bug。通常,我无法相信在这些库中发现错误,因为通常是我的错 :p,也许我实现得不正确...因此,任何帮助或提示都将不胜感激;)
2014 年 8 月更新:
看起来他们试图修复这个问题。
2015 年 5 月更新:
如果您安装了例如 VS 2015 RC,则该问题应该已经解决了,其中包括 .Net 4.6。我无法真正验证哪个版本的 .Net 修复了它,因为现在它在项目使用的所有版本中都可以工作。无论我将其设置为 .Net 4.5、4.5.1 还是 4.5.2,都无法再次重现该错误。