简体   繁体   English

为什么MemoryCache会抛出NullReferenceException

[英]Why does MemoryCache throw NullReferenceException

Update 更新

See updates below, issue is fixed the moment you install .Net 4.6. 请参阅下面的更新,安装.Net 4.6时问题已解决。


I want to implement something within the UpdateCallback of CacheItemPolicy . 我想在CacheItemPolicyUpdateCallback中实现一些东西。

If I do so and test my code running multiple threads on the same cache instance ( MemoryCache.Default ), I'm getting the following exception when calling the cache.Set method. 如果我这样做并测试我的代码在同一个缓存实例( 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#

I know that MemoryCache is thread safe so I didn't expect any issues. 我知道MemoryCache是线程安全的,所以我没想到任何问题。 More importantly, if I do not specify the UpdateCallback , everything works just fine! 更重要的是,如果我没有指定UpdateCallback ,一切正常!

Ok, for reproducing the behavior, here we go with some console app: This code is just a simplified version of some tests I'm doing for another library. 好的,为了重现行为,我们在这里使用一些控制台应用程序:这段代码只是我正在为另一个库做的一些测试的简化版本。 It is meant to cause collisions within a multithreaded environment, eg getting a condition where one thread tries to read a Key/Value while another thread already deleted it etc... 它意味着在多线程环境中引起冲突,例如获得一个线程试图读取键/值而另一个线程已经删除它等的条件......

Again, this should all work fine because MemoryCache is thread safe (but it doesn't). 同样,这应该都可以正常工作,因为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);
    }

}

You should directly get the exception if you run that. 如果你运行它,你应该直接获得异常。 If you comment out the UpdateCallback setter, you should not get an exception anymore. 如果您注释掉UpdateCallback setter,则不应再出现异常。 Also if you run only one thread (change Enumerable.Repeat<Action>(() => TestRun(), 10) to , 1) ), it will work just fine. 此外,如果您只运行一个线程(将Enumerable.Repeat<Action>(() => TestRun(), 10)更改为, 1) ),它将正常工作。

What I found so far: 到目前为止我发现了什么:

I found that whenever you set the Update or Remove callback, MemoryCache will create an additional sentinel cache entry for you with keys like OnUpdateSentinel<your key> . 我发现无论何时设置UpdateRemove回调, MemoryCache都会使用OnUpdateSentinel<your key>等密钥为您创建一个额外的标记缓存条目。 It seems that it also creates a change monitor on that item, because for sliding expiration, only this sentinel item will get the timeout set! 它似乎也在该项上创建了一个更改监视器,因为对于滑动过期,只有这个sentinel项才会获得超时设置! And if this item expires, the callback will get invoked. 如果此项目到期,则将调用回调。

My best guess would be that there is an issue within MemoryCache if you try to create the same item with the same key/policy/callback at roughly the same time, if we define the Callback... 我最好的猜测是,如果你尝试在大致相同的时间使用相同的键/策略/回调创建相同的项目,如果我们定义回调,则MemoryCache中存在问题...

Also as you can see from the stacktrace, the error appears somewhere within the Dispose method of the ChangeMonitor. 另外从堆栈跟踪中可以看出,错误出现在ChangeMonitor的Dispose方法中。 I didn't add any change monitors to the CacheItemPolicy so it seems to be something controlled internally... 我没有向CacheItemPolicy添加任何更改监视器,因此它似乎是内部控制的...

If this is correct, maybe this is a bug in MemoryCache. 如果这是正确的,也许这是MemoryCache中的一个错误。 I usually cannot believe finding bugs in those libraries because usually it is my fault :p, maybe I'm just too stupid to implement this correctly... So, any help or hints would be greatly appreciated ;) 我通常无法相信在这些库中发现错误,因为通常这是我的错:p,也许我只是太愚蠢而无法正确实现...所以,任何帮助或提示将不胜感激;)

Update Aug. 2014: 2014年8月更新:

Seems they try to fix this issue . 似乎他们试图解决这个问题

Update May 2015: 2015年5月更新:

Looks like the issue is fixed if you install eg the VS 2015 RC which comes with .Net 4.6. 如果安装例如.Net 4.6附带的VS 2015 RC,看起来问题已得到修复。 I cannot really verify which version of .Net fixes it because now it works in all versions the project uses. 我无法真正验证哪个版本的.Net修复它,因为现在它适用于项目使用的所有版本。 Doesn't matter if I set it to .Net 4.5, 4.5.1 or 4.5.2, the error is not reproduceable anymore. 如果我将其设置为.Net 4.5,4.5.1或4.5.2无关紧要,则错误不再可再现。

It would seem that Microsoft has fixed this, at least in .Net 4.5.2. 似乎微软已经解决了这个问题,至少在.Net 4.5.2中已经解决了这个问题。 Browsing referencesource.microsoft.com shows that there's now a lock around the access to the dictionary they're using to store internal data: 浏览referencesource.microsoft.com显示现在已经锁定了对用于存储内部数据的字典的访问:

MemoryCacheEntry.cs MemoryCacheEntry.cs

    internal void RemoveDependent(MemoryCacheEntryChangeMonitor dependent) {
        lock (this) {
            if (_fields._dependents != null) {
                _fields._dependents.Remove(dependent);
            }
        }
    }

I found this thread by NullReferenceException in memory cache. 我在内存缓存中通过NullReferenceException找到了这个线程。 My problem is receiving NullReferenceException when i was trying to add something to cache. 当我试图添加一些东西到缓存时,我的问题是接收NullReferenceException。

NullReferenceException
   at System.Runtime.Caching.MemoryCacheStore.UpdateExpAndUsage(MemoryCacheEntry entry, Boolean updatePerfCounters)
   at System.Runtime.Caching.MemoryCacheStore.AddOrGetExisting(MemoryCacheKey key, MemoryCacheEntry entry)
   at System.Runtime.Caching.MemoryCache.AddOrGetExistingInternal(String key, Object value, CacheItemPolicy policy)
   at System.Runtime.Caching.ObjectCache.Add(String key, Object value, CacheItemPolicy policy, String regionName)

MemoryCache is thread safe. MemoryCache是​​线程安全的。 We used one object in static field. 我们在静态字段中使用了一个对象。 Reason for NRE was one of other separate thread was trying to clear MemoryCache by calling cache.Dispose(); NRE的原因是其他一个单独的线程试图通过调用cache.Dispose()来清除MemoryCache; cache = new MemoryCache(); cache = new MemoryCache(); problem is easy to reproduce in just 2 parallel tasks: one task will add new objects second one will call Dispose and new MemoryCache, just after 0.5 second you will receive NRE somewhere incide MemoryCache .net 4.6.1 问题很容易在两个并行任务中重现:一个任务将添加新对象,第二个将调用Dispose和新的MemoryCache,在0.5秒之后,你将在某个地方收到NRE,包括MemoryCache .net 4.6.1

i just replaced .Dispose and new MemoryCache with foreach(var kv in cache){ cache.remove(kv.key) } 我刚用foreach(var kv in cache){ cache.remove(kv.key) }替换.Dispose和新的MemoryCache foreach(var kv in cache){ cache.remove(kv.key) }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么这会引发NullReferenceException? - Why does this throw a NullReferenceException? 为什么异步等待抛出NullReferenceException? - Why does async await throw a NullReferenceException? 为什么&#39;{&#39;在静态方法中抛出NullReferenceException? - Why does the '{' throw a NullReferenceException in a static method? 为什么调用一个为null的Action会引发NullReferenceException? - Why does calling an Action, that is null, throw NullReferenceException? 为什么这个不安全的代码会抛出NullReferenceException? - Why does this unsafe code throw a NullReferenceException? 为什么MemoryCache不抛出InvalidCastException - Why Doesn't MemoryCache throw an InvalidCastException 为什么异步扩展方法会抛出 System.NullReferenceException - Why does async extension method throw System.NullReferenceException 为什么MVC在似乎没有Null引用时会抛出NullReferenceException? - Why does MVC throw a NullReferenceException when there seems to be no Null Reference? 为什么可空 <T> HasValue属性不会在Null上抛出NullReferenceException吗? - Why does Nullable<T> HasValue property not throw NullReferenceException on Nulls? 为什么这段代码会引发NullReferenceException? 是因为引用字符串吗? - Why does this code throw a NullReferenceException? Is it because of the ref string?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM