【问题标题】:Dictionary lookup throws "Index was outside the bounds of the array"字典查找抛出“索引超出数组范围”
【发布时间】:2016-10-07 18:05:52
【问题描述】:

我收到了似乎来自以下代码的错误报告:

public class AnimationChannelCollection : ReadOnlyCollection<BoneKeyFrameCollection>
{
        private Dictionary<string, BoneKeyFrameCollection> dict =
            new Dictionary<string, BoneKeyFrameCollection>();

        private ReadOnlyCollection<string> affectedBones;

       // This immutable data structure should not be created by the library user
        internal AnimationChannelCollection(IList<BoneKeyFrameCollection> channels)
            : base(channels)
        {
            // Find the affected bones
            List<string> affected = new List<string>();
            foreach (BoneKeyFrameCollection frames in channels)
            {
                dict.Add(frames.BoneName, frames);
                affected.Add(frames.BoneName);
            }
            affectedBones = new ReadOnlyCollection<string>(affected);

        }

        public BoneKeyFrameCollection this[string boneName]
        {           
            get { return dict[boneName]; }
        }
}

这是读取字典的调用代码:

public override Matrix GetCurrentBoneTransform(BonePose pose)
    {
        BoneKeyFrameCollection channel =  base.AnimationInfo.AnimationChannels[pose.Name];       
    }

这是创建字典的代码,在启动时发生:

// Reads in processed animation info written in the pipeline
internal sealed class AnimationReader :   ContentTypeReader<AnimationInfoCollection>
{
    /// <summary> 
    /// Reads in an XNB stream and converts it to a ModelInfo object
    /// </summary>
    /// <param name="input">The stream from which the data will be read</param>
    /// <param name="existingInstance">Not used</param>
    /// <returns>The unserialized ModelAnimationCollection object</returns>
    protected override AnimationInfoCollection Read(ContentReader input, AnimationInfoCollection existingInstance)
    {
        AnimationInfoCollection dict = new AnimationInfoCollection();
        int numAnimations = input.ReadInt32();

        /* abbreviated */

        AnimationInfo anim = new AnimationInfo(animationName, new AnimationChannelCollection(animList));

    }
}

错误是:

索引超出了数组的范围。

行:0

在 System.Collections.Generic.Dictionary`2.FindEntry(TKey key)

在 System.Collections.Generic.Dictionary`2.get_Item(TKey key)

在 Xclna.Xna.Animation.InterpolationController.GetCurrentBoneTransform(BonePose 姿势)

我本来希望 KeyNotFoundException 带有错误的键,但我却得到“索引超出了数组的范围”。 我不明白我怎么能从上面的代码中得到那个异常?

顺便说一下,代码是单线程的。

【问题讨论】:

  • 代码无法编译。当然你的意思是return dict[boneName];
  • getter 的回报在哪里?
  • 此外,一旦添加正确的返回,我正确收到KeyNotFoundException。您需要更新您的代码以正确重现该问题,因为它目前没有。

标签: c# dictionary


【解决方案1】:

A “索引超出了数组的范围。” 当文档说不应该抛出错误时,字典(或 System.Collections 命名空间中的任何内容)上的错误是 总是是你违反线程安全造成的。

System.Collections 命名空间中的所有集合只允许发生两种操作中的一种

  • 无限并发读取器,0 个写入器。
  • 0 位读者,1 位作者。

您要么必须使用ReaderWriterLockSlim 同步对字典的所有访问,这给出了上述确切行为

private Dictionary<string, BoneKeyFrameCollection> dict =
            new Dictionary<string, BoneKeyFrameCollection>();
private ReaderWriterLockSlim dictLock = new ReaderWriterLockSlim();

public BoneKeyFrameCollection this[string boneName]
{           
    get 
    { 
        try
        {
            dictLock.EnterReadLock();
            return dict[boneName]; 
        }
        finally
        {
            dictLock.ExitReadLock();
        }
    }
}


 public void UpdateBone(string boneName, BoneKeyFrameCollection col)
 {  
    try
    {
        dictLock.EnterWriteLock();
        dict[boneName] = col; 
    }
    finally
    {
        dictLock.ExitWriteLock();
    }
 }

或将您的Dictionary&lt;string, BoneKeyFrameCollection&gt; 替换为ConcurrentDictionary&lt;string, BoneKeyFrameCollection&gt;

private ConcurrentDictionary<string, BoneKeyFrameCollection> dict =
            new ConcurrentDictionary<string, BoneKeyFrameCollection>();

 public BoneKeyFrameCollection this[string boneName]
 {           
    get 
    { 
        return dict[boneName];
    }
 }

 public void UpdateBone(string boneName, BoneKeyFrameCollection col)
 {  
    dict[boneName] = col;
 }

更新:我真的不明白您显示的代码是如何导致这种情况的。 Here is the source code 是导致它被抛出的函数。

private int FindEntry(TKey key) {
    if( key == null) {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
    }

    if (buckets != null) {
        int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
        for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) {
            if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
        }
    }
    return -1;
}

代码抛出ArgumentOutOfRangeException 的唯一方法是,如果您尝试索引bucketsentries 中的非法记录。

因为您的键是 string 并且字符串是不可变的,我们可以排除在将键放入字典后更改的键的 hashcode 值。剩下的就是一个buckets[hashCode % buckets.Length] 电话和几个entries[i] 电话。

buckets[hashCode % buckets.Length] 可能失败的唯一方法是 buckets 的实例在 buckets.Length 属性调用和 this[int index] 索引器调用之间被替换。唯一一次替换buckets 是当ResizeInsert 内部调用、Initialize 被构造函数调用/第一次调用Insert 或调用OnDeserialization 时。

唯一调用Insert 的地方是this[TKey key] 的设置器、公共Add 函数和OnDeserialization 内部。替换buckets 的唯一方法是,如果我们同时调用三个列出的函数之一,FindEntry 调用在buckets[hashCode % buckets.Length] 调用期间发生在另一个线程上。

我们得到错误的entries[i] 调用的唯一方法是,如果entries 被我们换掉(遵循与buckets 相同的规则),或者我们得到i 的错误值。获得i 错误值的唯一方法是entries[i].next 返回错误值。从entries[i].next 获得错误值的唯一方法是在InsertResizeRemove 期间进行并发操作。

我能想到的唯一一件事是OnDeserialization 调用出现问题并且在反序列化之前您有错误的数据要开始,或者AnimationChannelCollection 有更多代码会影响您未显示的字典我们。

【讨论】:

  • 太好了。但我相信代码是单线程的......但至少我可以排除丢失键是错误的根源。
  • 动画在开始时被加载到只读集合中。但错误发生在正常帧更新期间的游戏会话中。游戏加载后不会修改集合。所以我看不出作者应该来自哪里会导致异常。
  • 显示如何完成初始加载作为对问题的编辑。问题很可能就在那里。我从未看到这个问题不是“我在一个集合上使用了多个并发编写器”作为根本原因。
  • @user1919998 唯一可能可能的另一件事是,如果您使用的密钥更改了它自己的 HashCode().Equals() 行为集合的关键。但是,由于您的密钥是 string,我发现它极不可能,除非您在创建字典时做了一些奇怪的非标准 IEqualityComparer&lt;string&gt;
  • 我只在构造函数中添加了显示字典是如何写入的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多