【发布时间】:2014-03-07 23:23:16
【问题描述】:
更新
请参阅下面的更新,安装 .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,一切正常!
好的,为了重现行为,这里我们使用一些控制台应用程序: 这段代码只是我为另一个库做的一些测试的简化版本。它旨在引起多线程环境中的冲突,例如出现一个线程尝试读取键/值而另一个线程已经删除它等情况...
同样,这一切都应该正常工作,因为 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 都会为您创建一个额外的 sentinel 缓存条目,其中包含 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,都无法再重现该错误。
【问题讨论】:
-
是的解决方案是不使用它^^。对于滑动过期,我最后还使用绝对过期并在获取时手动更新元素(过期超时)。这样它工作得很好......见cachemanager.codeplex.com/SourceControl/latest#src/…`1.cs方法'GetCacheItemInternal'
-
如果你能复制它,你可能也想评价这个:connect.microsoft.com/VisualStudio/feedback/details/817211/…
-
这方面有什么更新吗?
-
根据@MichaC 添加的链接,它已解决...某处connect.microsoft.com/VisualStudio/feedback/details/817211/…
-
我在 Win8 上几秒钟内重复出现异常,但在 Server 2012 R2 上 10 分钟后仍然没有异常。
标签: c# .net multithreading caching .net-4.5