2014-02-10 2 views
22

업데이트이유는 NullReferenceException이

아래, 문제는 닷넷 4.6를 설치하는 순간을 고정 업데이트를 참조하십시오 던져 MemoryCache 않습니다.


나는 CacheItemPolicyUpdateCallback 내에서 뭔가를 구현하고자합니다.

동일한 캐시 인스턴스 (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 콜백을 설정할 때마다, MemoryCacheOnUpdateSentinel<your key> 같은 키를 당신을 위해 추가 감시 캐시 항목을 만들 것을 발견 : 나는 지금까지 무엇을 발견

. 슬라이딩 만료의 경우이 센티널 아이템 만 타임 아웃 세트를 가져 오기 때문에 해당 아이템에 대한 변경 모니터도 생성되는 것으로 보입니다! 이 항목이 만료되면 콜백이 호출됩니다.

내 추측은 ... 우리가 콜백을 정의 할 경우, 대략 같은 시간 같은 키/정책/콜백과 같은 항목을 만들려고하면 MemoryCache 내에서 문제가 있다는 것

또한 stacktrace에서 볼 수 있듯이 오류는 ChangeMonitor의 Dispose 메서드 내 어딘가에 나타납니다. CacheItemPolicy에 변경 모니터를 추가하지 않았으므로 내부적으로 제어되는 것으로 보입니다 ...

이것이 맞으면 아마도 MemoryCache의 버그 일 수 있습니다. 나는 대개 그게 내 잘못이라고 생각하기 때문에 그 도서관에서 벌레를 찾는 것을 믿을 수 없다 : p, 어쩌면 나는 이것을 올바르게 구현하기에는 너무 어리 석다 ... 어떤 도움이나 힌트도 크게 평가 될 것이다;)

업데이트 2014 년 8 월 :

this issue을 수정하려고합니다.

업데이트 할 수 있습니다 2015 당신이, 예를 들어 설치하는 경우

는 문제가 해결 같은데 .Net 4.6과 함께 제공되는 VS 2015 RC. .Net이 프로젝트의 모든 버전에서 작동하기 때문에 .Net의 어떤 버전을 수정했는지 확인할 수 없습니다. .Net 4.5, 4.5.1 또는 4.5.2로 설정하면 오류가 더 이상 재생성되지 않습니다.

+3

그래 솔루션 .Dispose 새로운 MemoryCache을 교체했다. 슬라이딩 만료에 대해서도 나는 마지막에 절대 만료를 사용하고 get에서 요소 (만료 시간 초과)를 수동으로 업데이트합니다. 이 ... 가 잘 볼 작품이 방법 https://cachemanager.codeplex.com/SourceControl/latest#src/CacheManager.SystemRuntimeCaching/MemoryCacheHandle'1.cs 방법 당신이 그것을 재현 할 수있는 경우 'GetCacheItemInternal' – MichaC

+0

하면, 당신은 할 수 있습니다 https://connect.microsoft.com/VisualStudio/feedback/details/817211/memorycache-throw-nullreferenceexception-if-cacheitempolicy-updatecallback-is-defined#tabs이에 – MichaC

+0

업데이트 : 이것도 하나를 속도로? –

답변

1

그것은 마이크로 소프트가 적어도 닷넷 4.5.2에서,이 문제를 해결 한 것으로 보인다. 브라우징 referencesource.microsoft.com은 지금은 내부 데이터를 저장하는 데 사용하는 사전에 대한 액세스 주위에 잠금 장치가 있음을 보여줍니다

MemoryCacheEntry.cs

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

내가 메모리 캐시에서 NullReferenceException이 에 의해이 스레드를 발견했다. 캐시에 무언가를 추가하려고 할 때 내 문제가 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) 

메모리 캐시는 스레드로부터 안전합니다. 정적 필드에서 하나의 객체를 사용했습니다. NRE에 대한 이유는 다른 별도의 스레드가 cache.Dispose(); cache = new MemoryCache(); 하나의 작업은 단지 0.5 초 후에 당신이 어딘가에는 incide MemoryCache .NET 4.6.1

을 NRE를 받게됩니다, 새로운 객체를 폐기하고 새로운 MemoryCache를 호출합니다 두 번째를 추가합니다 : 문제는 단지 2 병렬 작업에서 재현하기 쉽습니다 난 그냥 ** ** ^^ 그것을 사용하지 않는 것입니다 foreach(var kv in cache){ cache.remove(kv.key) }

관련 문제