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_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,第一个人获得异常 - 集合修改等等
我明白这个问题 - 它的发生是因为我们正在访问相同的内存位。我有 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。
问题是缓存对象对于 appdomain 是全局的,并且存储在其中的数据在所有请求之间共享。
解决这个问题的唯一方法是在你想访问集合时激活一个锁,然后释放锁(https://msdn.microsoft.com/en-us/library/vstudio/c5kehkcz%28v=vs.100%29.aspx)。
(对不起,我的英语不好)
好的。这是支持团队给出的一些异常信息。我知道它发生的行和代码。它发生在对从缓存中获取的字典的 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,第一个人获得异常 - 集合修改等等
我明白这个问题 - 它的发生是因为我们正在访问相同的内存位。我有 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。
问题是缓存对象对于 appdomain 是全局的,并且存储在其中的数据在所有请求之间共享。 解决这个问题的唯一方法是在你想访问集合时激活一个锁,然后释放锁(https://msdn.microsoft.com/en-us/library/vstudio/c5kehkcz%28v=vs.100%29.aspx)。 (对不起,我的英语不好)