ASP.net cache access causing Collection Modified exception in foreach loop

asp.net, caching, linq, locking, race-condition

Solution

This happens because you're working directly with object, locating in cache. Good practise, to avoid those exceptions and other wierd behavior (when you accidentally modify cache object) is working with copy of cache data. And there are several ways of achieving it, like doing clone or some kind of deep copy. What i prefer is keeping objects in cache serialized (any kind you like - json/xml/binary or w/e else), since (de)serialization makes a deep copy of your object. Following small code snippet will clarify things:

public static class CacheManager
{
    private static readonly Cache MyCache = HttpRuntime.Cache;

    public static void Put<T>(T data, string key)
    {
        MyCache.Insert(key, Serialize(data));
    }

    public static T Get<T>(string key)
    {
        var data = MyCache.Get(key) as string;
        if (data != null)
            return Deserialize<T>(data);
        return default(T);
    }

    private static string Serialize(object data)
    {
        //this is Newtonsoft.Json serializer, but you can use the one you like  
        return JsonConvert.SerializeObject(data);
    }

    private static T Deserialize<T>(string data)
    {
        return JsonConvert.DeserializeObject<T>(data);
    }
}

And usage:

var myObj = new Dictionary<int, int>();
CacheManager.Put(myObj, "myObj");
//...
var anotherObj = CacheManager.Get<Dictionary<int, int>>("myObj");

Problem

Ok first things first. This is some exception information given by the support team. I know the line and code where it happens. It happens in a FirstOrDefault call over a dictionary obtained from cache. ``` 1) Exception Information ********************************************* Exception Type: System.InvalidOperationException Message: Collection was modified; enumeration operation may not execute. Data: System.Collections.ListDictionaryInternal ``` Now I wanted to simulate the problem and I could do it in a simple ASP.net application. My page has 2 Buttons - Button_Process and Button_Add The code behind is as follows: ``` public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { var data = Cache["key"]; if (data == null) { var dict = new Dictionary<int, string>(); for (int i = 0; i < 10; i++) { dict.Add(i, "i"); } Cache["key"] = dict; } } } protected void ButtonProcess_Click(object sender, EventArgs e) { var data = Cache["key"] as Dictionary<int, string>; if (data != null) { foreach (var d in data.Values) //In actual code there is FirstOrDefault here { Thread.Sleep(1000); if (d.Contains("5")) { //some operation } } } } protected void Button2_Click(object sender, EventArgs e) { var data = Cache["key"] as Dictionary<int, string>; if (data != null) { data.Add(new Random().Next(), "101"); Cache["key"] = data; } } } ``` Now assume there are 2 requests: Request 1 - Someone clicks on button_Process and some operation on cache object is taking place Request 2 - Someone clicks on button_Add and the first person gets an exception - collection modified blah blah I understand the problem - it is happening because we are accessing same bit of memory. I have 2 solutions in my mind: 1. I use a for loop instead of for each (to replace FirstOrDefault in actual code) - I dunno how efficient this operation will be after I make the changes. - I don't ever delete any item from cache so I was thinking of this solution 2. I put some lock over cache object or something on those lines - but I dunno exactly where and how should I lock this object. Please help me with this. I am not able to figure out an efficient solution. What is the best way to handle such situations?

Original source