可能导致“目标阵列不够长"的原因是:在List.Add中的单个线程中? [英] What could cause "Destination array was not long enough" during List.Add in a single thread?

查看:100
本文介绍了可能导致“目标阵列不够长"的原因是:在List.Add中的单个线程中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个要在嵌套的foreach循环中添加到的对象列表.该操作是同步的(或者也许我不像我想的那样理解lambda)并且是单线程的,并且列表并不是不合理的.我对可能导致此异常的原因一无所知.

I have a List of objects that I'm adding to in nested foreach loops. the operation is synchronous (or maybe I don't understand lambdas as well as I think I do) and single-threaded and the list isn't unreasonably big. I'm at a total loss for what could be causing this exception.

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)

试图通过一些单元测试来涵盖这一点并加以完善,但我希望在此期间有人可以帮助我.

Trying to cover this with some unit tests and hammer on it, but I'm hoping someone can help me out in the mean time.

-编辑-

感谢Juan和Mark,我弄清楚了这是怎么发生的.在我的应用程序中,此操作本身是单线程的,但是它使用的本质上是单例的,并通过ajax调用.多个调用者可以启动他们自己的线程,并且当这些调用足够接近时,我们就会得到这种行为.我制作了一个控制台应用程序来说明这个概念.

Thanks to Juan and Mark I've figured out how this could happen. In my application, this operation is single threaded itself, but it uses what is essentially a singleton and is invoked via ajax. Multiple callers can start their own thread, and when those invocations are close enough together we get this behavior. I've made a console app to illustrate the concept.

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方法一起抛出.

The exception or a similar exception is thrown most of the time, but not every time. It's never thrown with the Fixed method.

推荐答案

您是正确的.处理列表的代码不使用线程.

You are correct. The code that is manipulating the list does not use threading.

但是,我认为有些事情在先前的运行有机会完成之前(因此引入了线程),反复调用 UpdateInstanceGraph .这将导致 ElementInstances 在先前的调用仍在执行时被重置为大小 0 .

However, I think something is calling UpdateInstanceGraph repeatedly before the prior run has a chance to complete (and thus introducing threading). That would cause ElementInstances to be reset to size 0 while the prior call is still executing.

将代码更改为使用本地实例,然后设置公共属性:

Change your code to use a local instance instead and then set the public property:

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 以便直接分配给该属性:

I would also recommend using SelectMany instead and casting to a List for direct assignment to the property:

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

这篇关于可能导致“目标阵列不够长"的原因是:在List.Add中的单个线程中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆