ASP.net缓存访问导致foreach循环中的Collection Modified异常

时间:2015-02-24 06:17:08

标签: asp.net linq caching locking race-condition

首先要做的事情。这是支持团队提供的一些异常信息。我知道它发生的行和代码。它发生在通过缓存获取的字典的FirstOrDefault调用中。

1) Exception Information
*********************************************
Exception Type: System.InvalidOperationException

Message: Collection was modified; enumeration operation may not execute.

Data: System.Collections.ListDictionaryInternal

现在我想模拟问题,我可以在一个简单的ASP.net应用程序中完成。
我的页面有2个按钮 - Button_Process Button_Add
背后的代码如下:

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

现在假设有2个请求:

请求1 - 有人点击了button_Process,并且正在对缓存对象进行一些操作 请求2 - 有人点击button_Add并且第一个人获得了异常 - 收集修改后的等等等等

我理解这个问题 - 它正在发生,因为我们正在访问相同的内存。我脑子里有两个解决方案:
1。我使用for循环而不是每个(在实际代码中替换FirstOrDefault) - 我不知道在进行更改后此操作的效率如何。 - 我不会从缓存中删除任何项目,所以我在想这个解决方案 2。我对缓存对象或其他东西进行了一些锁定 - 但我确切地知道我应该在哪里以及如何锁定这个对象。

请帮帮我。我无法找到有效的解决方案。处理此类情况的最佳方法是什么?

3 个答案:

答案 0 :(得分:3)

这是因为您直接使用对象,位于缓存中。好的做法是,避免这些异常和其他奇怪的行为(当您意外修改缓存对象时)正在使用缓存数据的 copy 。有几种方法可以实现它,例如执行 clone 或某种深层复制。我更喜欢将对象保存在高速缓存中序列化(您喜欢的任何类型 - json / xml / binary或w / e else),因为(de)序列化会对您的对象进行深层复制。以下小代码片段将澄清事情:

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

用法:

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

答案 1 :(得分:1)

检查.NET 3.5的任务并行库。它具有ConcurrentStctions,例如ConcurrentStack,ConcurentQueue和ConcurrentDictionary。

http://www.nuget.org/packages/TaskParallelLibrary

答案 2 :(得分:1)

问题是缓存对象对于appdomain是全局的,并且存储的数据在所有请求之间共享。 解决此问题的唯一方法是在您想要访问集合时激活锁定,然后释放锁定(https://msdn.microsoft.com/en-us/library/vstudio/c5kehkcz%28v=vs.100%29.aspx)。 (对不起,我的英文不好)