c# - 在单线程中的 List.Add 期间,什么可能导致 "Destination array was not long enough"?

标签 c# arrays list

我有一个要添加到嵌套 foreach 循环中的对象列表。该操作是同步的(或者我可能并不像我认为的那样理解 lambda)并且是单线程的,并且列表不是不合理的大。对于可能导致此异常的原因,我完全不知所措。

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 方法抛出。

最佳答案

你是对的。处理列表的代码不使用线程。

但是,我认为在先前的运行有机会完成之前,某些东西正在重复调用 UpdateInstanceGraph(从而引入线程)。这将导致 ElementInstances 在先前的调用仍在执行时重置为大小 0

更改您的代码以改为使用本地实例,然后设置公共(public)属性:

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

关于c# - 在单线程中的 List.Add 期间,什么可能导致 "Destination array was not long enough"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50240932/

相关文章:

swift - SwiftUI列表中的自定义按钮

c# - 列出 <class> 按字母顺序排列,同时将某些项目保持在顶部?

c# - 引用 com 程序集 dll 时出现问题

c# - 在 ASP.NET MVC 中使用 ExpandoObject 时出错

java - 在Java中调用多维数组的更好方法是什么?

javascript - 浏览器自动关闭列表标签

python:递归列表处理更改原始列表

c# - 如何覆盖 &&(逻辑和运算符)?

android - 从改造响应中获取 JSONArray

javascript - 查找没有删除项的 Javascript 数组长度