net framework cache c# .net multithreading caching .net-4.5

c# - framework - ¿Por qué MemoryCache lanza NullReferenceException?



memory cache c# (2)

Actualizar

Consulte las actualizaciones a continuación, el problema se soluciona en el momento de instalar .Net 4.6.

Quiero implementar algo dentro de UpdateCallback de CacheItemPolicy .

Si lo hago y MemoryCache.Default mi código ejecutando varios subprocesos en la misma instancia de caché ( MemoryCache.Default ), recibo la siguiente excepción cuando llamo al método 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#

Sé que MemoryCache es seguro para subprocesos, así que no esperaba ningún problema. Más importante aún, si no especifico UpdateCallback , ¡todo funciona bien!

Ok, para reproducir el comportamiento, aquí vamos con alguna aplicación de consola: Este código es solo una versión simplificada de algunas pruebas que estoy haciendo para otra biblioteca. Está destinado a causar colisiones dentro de un entorno multiproceso, por ejemplo, obtener una condición donde un hilo intenta leer una clave / valor mientras otro hilo ya lo borró, etc.

De nuevo, todo debería funcionar bien porque MemoryCache es seguro para subprocesos (pero no lo es).

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); } }

Debería obtener directamente la excepción si ejecuta eso. Si comenta el setter de UpdateCallback, ya no debería recibir una excepción. Además, si ejecuta solo un hilo (cambie Enumerable.Repeat<Action>(() => TestRun(), 10) to , 1) ), funcionará perfectamente.

Lo que encontré hasta ahora:

Descubrí que cada vez que configuras la Update o MemoryCache devolución de llamada, MemoryCache creará una entrada de caché centinela adicional para ti con claves como OnUpdateSentinel<your key> . Parece que también crea un monitor de cambio en ese artículo, porque para la expiración deslizante, ¡solo este artículo centinela obtendrá el tiempo de espera establecido! Y si este elemento caduca, se invocará la devolución de llamada.

Mi mejor opción sería que hay un problema en MemoryCache si intentas crear el mismo elemento con la misma clave / política / devolución de llamada aproximadamente al mismo tiempo, si definimos la devolución de llamada ...

Además, como puede ver en la pila de seguimiento, el error aparece en algún lugar dentro del método Dispose del ChangeMonitor. No CacheItemPolicy ningún monitor de cambios a CacheItemPolicy así que parece ser algo controlado internamente ...

Si esto es correcto, tal vez este es un error en MemoryCache. Normalmente no puedo creer encontrar errores en esas bibliotecas porque generalmente es mi culpa: p, tal vez soy demasiado estúpido para implementar esto correctamente ... Por lo tanto, cualquier ayuda o sugerencia sería muy apreciada;)

Actualización agosto de 2014:

Parece que intentan solucionar este problema .

Actualización de mayo de 2015:

Parece que el problema está solucionado si instala, por ejemplo, el VS 2015 RC que viene con .Net 4.6. Realmente no puedo verificar qué versión de .Net lo corrige porque ahora funciona en todas las versiones que usa el proyecto. No importa si lo configuro para .Net 4.5, 4.5.1 o 4.5.2, el error ya no se puede reproducir.


Encontré este hilo por NullReferenceException en memoria caché. Mi problema es recibir NullReferenceException cuando estaba tratando de agregar algo a la memoria caché.

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 es seguro para subprocesos. Usamos un objeto en el campo estático. La razón de NRE era uno de los hilos separados que estaba intentando borrar MemoryCache llamando a cache.Dispose (); cache = new MemoryCache (); El problema es fácil de reproducir en solo 2 tareas paralelas: una tarea agregará nuevos objetos, el segundo llamará Dispose y New MemoryCache, justo después de 0.5 segundos recibirá NRE en algún lugar incide MemoryCache .net 4.6.1

acabo de reemplazar. Deshacer y nuevo MemoryCache con foreach(var kv in cache){ cache.remove(kv.key) }


Parece que Microsoft ha solucionado esto, al menos en .Net 4.5.2. La exploración de referencesource.microsoft.com muestra que ahora existe un bloqueo alrededor del acceso al diccionario que están utilizando para almacenar datos internos:

MemoryCacheEntry.cs

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