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

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

好的。这是支持团队给出的一些异常信息。我知道它发生的行和代码。它发生在对从缓存中获取的字典的 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_ProcessButton_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,第一个人获得异常 - 集合修改等等

我明白这个问题 - 它的发生是因为我们正在访问相同的内存位。我有 2 个解决方案:
1. 我使用 for 循环而不是 for each(以替换实际代码中的 FirstOrDefault)- 我不知道进行更改后此操作的效率如何。 - 我从不从缓存中删除任何项目,所以我在考虑这个解决方案
2. 我在缓存对象或那些行上加了一些锁 - 但我不知道我应该在哪里以及如何锁定这个对象。

请帮我解决这个问题。我无法找出有效的解决方案。处理这种情况的最佳方法是什么?

发生这种情况是因为您直接使用位于缓存中的对象。避免这些异常和其他奇怪行为(当您不小心修改缓存对象时)的好习惯是使用 copy 缓存数据。有几种方法可以实现它,比如 clone 或某种 deep copy。我更喜欢将缓存中的对象保持序列化(任何你喜欢的类型 - json/xml/binary 或 w/e 其他),因为(反)序列化对你的对​​象进行深度复制。以下小代码片段将澄清事情:

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

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

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

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