我在嵌套的foreach循环中添加了一个对象列表。操作是同步的(或者我不理解lambdas以及我认为我做的)和单线程,并且列表并不是不合理的大。因为导致这种异常的原因,我完全失去了。
public string PromotionSpecificationIdGuid { get; set; }
public virtual List<ElementInstance> ElementInstances { get; set; }
public void UpdateInstanceGraph(OfferingInstance parentData, OfferingInstance offeringContainer = null)
{
ElementInstances = new List<ElementInstance>();
parentData.ActiveServices.ForEach(
service => service.ActiveComponents.ForEach(
component => component.Elements.ForEach(
element =>
{
if (element.PromotionId == this.PromotionSpecificationIdGuid)
{
ElementInstances.Add(element);
}
})));
}
结果是:
System.ArgumentException: Destination array was not long enough. Check destIndex and length, and the array's lower bounds.
at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
at System.Collections.Generic.List`1.set_Capacity(Int32 value)
at System.Collections.Generic.List`1.EnsureCapacity(Int32 min)
at System.Collections.Generic.List`1.Add(T item)
尝试通过一些单元测试来解决这个问题并对其进行锤击,但我希望有人可以帮助我。
- 编辑 -
感谢Juan和Mark我已经弄清楚这是怎么发生的。在我的应用程序中,此操作本身是单线程的,但它使用基本上是单例并通过ajax调用。多个调用者可以启动自己的线程,当这些调用足够接近时,我们会得到这种行为。我已经制作了一个控制台应用来说明这个概念。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace listaccessmonster
{
public class Program
{
private static List<Guid> baseList = new List<Guid>();
private static List<Guid> activeList;
private static Random rand = new Random();
public static void Main(string[] args)
{
for(int i = 0; i < 1000000; i++)
{
baseList.Add(Guid.NewGuid());
}
var task1 = UpdateList(); //represents ajax call 1
var task2 = UpdateList(); //represents ajax call 2
var result = Task.WhenAll(task1, task2);
try
{
result.Wait();
}
catch(Exception e)
{
Console.WriteLine(e);
}
task1 = UpdateListFixed(); //represents ajax call 1
task2 = UpdateListFixed(); //represents ajax call 2
result = Task.WhenAll(task1, task2);
try
{
result.Wait();
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.WriteLine("press Enter to exit");
Console.ReadKey();
}
private static Task UpdateList()
{
return Task.Run(()=> {
Thread.Sleep(rand.Next(5));
Console.WriteLine("Beginning UpdateList");
activeList = new List<Guid>();
baseList.ForEach(x => {
activeList.Add(x);
});
});
}
private static Task UpdateListFixed()
{
return Task.Run(() => {
Thread.Sleep(rand.Next(5));
Console.WriteLine("Beginning UpdateListFixed");
var tempList = new List<Guid>();
baseList.ForEach(x => {
tempList.Add(x);
});
activeList = tempList;
});
}
}
}
大多数情况下会抛出异常或类似的异常,但不是每次都抛出异常。使用Fixed方法永远不会抛出它。
答案 0 :(得分:2)
你是对的。操作列表的代码不使用线程。
但是,我认为在先前的运行有机会完成之前反复调用UpdateInstanceGraph
(从而引入线程)。这会导致ElementInstances
在前一次调用仍在执行时重置为0
。
将代码更改为使用本地实例,然后设置公共属性:
public void UpdateInstanceGraph(OfferingInstance parentData, OfferingInstance offeringContainer = null)
{
var instances = new List<ElementInstance>();
parentData.ActiveServices.ForEach(
service => service.ActiveComponents.ForEach(
component => component.Elements.ForEach(
element =>
{
if (element.PromotionId == this.PromotionSpecificationIdGuid)
{
instances.Add(element);
}
})));
ElementInstances = instances;
}
我还建议您使用SelectMany
代替并投射到List
以直接分配给该媒体资源:
public void UpdateInstanceGraph(OfferingInstance parentData, OfferingInstance offeringContainer = null)
{
ElementInstances = parentData.ActiveServices
.SelectMany(s => s.ActiveComponents)
.SelectMany(c => c.Elements)
.Where(e => e.PromotionId == PromotionSpecificationIdGuid).ToList();
}
答案 1 :(得分:2)
我认为JuanR接近正确,但不完全正确。这肯定是一个线程问题,它肯定来自您发布的代码之外。但它可能是也可能不是UpdateInstanceGraph
的并发调用,如果是,它们同时运行add
方法。[1]
问题是对单个List
对象实例的方法进行并发访问。我们知道其中一个主题是尝试add
List
(UpdateInstanceGraph
的“最内层”语句)的元素。另一个线程可以从程序中的任何位置执行代码,对List
实例执行任何操作,因为您已为列表提供了公共getter。
您可以从List
切换到线程安全的实现。我猜.NET 1.0中有ArrayList
;但MS文档表明,与较新的(.NET 4.0)线程安全类相比,这不是非常有效。问题是,我似乎无法在较新的类中找到简单的List
类型。
另一种选择是在应用程序使用此List
对象的任何地方管理并发性,但这很容易出错。另一种选择是围绕List
编写一个线程安全的包装器,但是我看不出使用ArrayList
会更有效。
好吧,无论如何,我知道你说你已经检查并重新检查了没有并发问题,但如果应用程序本身有任何并发概念,那么给定该属性的公共getter,我可以不知道你怎么知道那个;并且有证据表明事实并非如此。
[1]我说两个线程必须同时运行add
的原因是,如果它是“第二次”调用“将大小重置为零”的问题,那就不会有这种症状。有问题的陈述
ElementInstances = new List<ElementInstance>();
不会将List对象的大小更改为0;它创建一个新的List对象(其大小为0),并将ElementInstances
的引用更改为此新实例。在发生这种情况时,已经在“第一次”调用中启动的任何add
调用都将完成(将该元素成功添加到不再引用的列表中);并且任何尚未启动的add
调用将从新对象开始(成功将元素添加到新列表中......最终您会注意到先前的元素丢失,但这是完全不同的症状)。
它可以同时访问单个List
实例的方法,这些方法可能导致这些奇怪的异常。