ObservableCollection的FirstOrDefault获取ArgumentOutOfRange异常

时间:2018-03-21 13:54:34

标签: c# linq

我有一个Observable Collection CurrentItemSource,当我尝试拨打ArgumentOutOfRangeException时,我会收到FirstOrDefault

CommandViewModel item = CurrentItemSource?.FirstOrDefault();

我不明白这是怎么回事,因为enter image description here描述了FirstOrDefault中唯一可以抛出的异常是ArgumentNullException

我得到的例外:

  

ArgumentOutOfRangeException [2]:Der IndexlagaußerhalbdesBereichs。 Er darf nicht negativ und kleiner als die Auflistung sein。     参数名称:索引

怎么会发生这种情况,是否有可能解决这个问题?

我在构造函数中实例化CurrentItemSource

CurrentItemSource = new ObservableCollection<CommandViewModel>();

if (Application.Current != null)
{
    Application.Current.Dispatcher.BeginInvoke(new Action(() => 
    { 
        CurrentItemSource.EnableCollectionSynchronization();
    }));
}

然后我还使CollectionSynchronization具有线程安全的ObservableCollection。

我的XAML文件中也有一个绑定:

<ComboBox ItemsSource="{Binding CurrentItemSource}"/>

FirstOrDefault的上下文 - 致电:

if (ComboBoxText.IsNullOrEmpty())
{
    //Do Something
}
else
{
    CommandViewModel item = CurrentItemSource?.FirstOrDefault(); //Original Line

    if (item != null)
    {
        if (item is Type1)
        {
            //Do Something
        }
        else if (item is Type2)
        {
            //Do Something else
        }
    }   
}

堆栈跟踪:

Stacktrace [2]
       bei System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
       bei System.Collections.Generic.List1.get_Item(Int32 index)
       bei System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable1 source)
       bei Gui.ViewModels.ComboBoxViewModel.SetDisplayedText(String comboBoxText) in D:\workspace\Gui\ViewModels\ComboBoxViewModel.cs:Zeile 707.
       bei Gui.ViewModels.ComboBoxViewModel.set_ComboBoxText(String value) in D:\workspace\Gui\ViewModels\ComboBoxViewModel.cs:Zeile 209.
       bei Gui.ViewModels.OutputViewModel.CreateContextMenu(ComboBoxViewModel comboBox) in D:\workspace\Gui\ViewModels\OutputViewModel.cs:Zeile 160.
       bei Gui.ViewModels.ComboBoxViewModel.CreateContextMenuAsync() in D:\workspace\Gui\ViewModels\ComboBoxViewModel.cs:Zeile 1051.
       bei Gui.ViewModels.ComboBoxViewModel.<BuildCurrentItemSource>b__135_0() in D:\workspace\Gui\ViewModels\ComboBoxViewModel.cs:Zeile 1031.
       bei System.Threading.Tasks.Task.Execute()
    --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---
       bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
       bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       bei System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
       bei Gui.ViewModels.ComboBoxViewModel.<BuildCurrentItemSource>d__135.MoveNext() in D:\workspace\Gui\ViewModels\ComboBoxViewModel.cs:Zeile 1031.

1 个答案:

答案 0 :(得分:2)

异常原因的简要摘要

您的代码因为对IList<T>所代表的基础ObservableCollection的线程不安全访问而引发异常。

EnableCollectionSynchronization不提供自己的线程安全性。它只是保证CustomView将使用相同的同步技术以线程安全的方式访问集合的元素,就像提供一样。< / p>

请注意,文档为:

  

虽然您必须同步应用程序对该应用程序的访问权限   集合,你还必须保证从WPF访问(特别是   来自CollectionView)参与同一个同步   机制。您可以通过调用EnableCollectionSynchronization来执行此操作   方法

<强>解决方案

由于您使用的是parameterless overload,因此您需要通过lock语句(或System.Threading.Monitor类)保护您对可观察集合的访问权限。

因此,您必须这样做:

// class level field
private object _lock = new object();

...

CommandViewModel item = null;

lock(_lock)
{
  item = CurrentItemSource?.FirstOrDefault();
}

有关异常原因的更多说明

异常的来源很可能是以下两个地方之一。如果发布堆栈跟踪,您可能会看到它在其路径中有两个代码路径之一:

一:System.Linq.Enumerable.FirstOrDefault<TSource>()

if条件,检查基础列表的可为空性,然后进行索引。

public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source)
{
    IList<TSource> list = source as IList<TSource>;

    // The following is re-entrant by multiple threads
    // in a thread-unsafe manner.
    if (list != null)
    {
        if (list.Count > 0)
        {
            // This code is contentious and most likely
            // the place where your code bombs.
            return list[0];
        }
    }
    else
        ...

二:MoveNext类的System.Collections.Generic.List<T>+Enumerator<T>方法

由于您调用ObservableCollection类的无参数构造函数,因此默认使用System.Collections.Generic.List<T>,默认情况下该线程不安全。当您或WPF框架在集合上编写foreach的任何代码时,将调用MoveNext类的List<T>方法,该方法调用MoveNext的实现名为Enumerator<T>的私有/嵌套类中的方法。这可能是线程不安全代码可能抛出ArgumentOutOfRangeException的另一个地方。

public bool MoveNext()
{
    List<T> list = this.list;

    // This if condition might be entered into by a second thread
    // after the first thread modified the list
    if ((this.version == list._version) && (this.index < list._size))
    {
        // This line is contentious and might be
        // the source of your exception.
        this.current = list._items[this.index];


        this.index++;
        return true;
    }
    return this.MoveNextRare();
}

正如您现在发布的堆栈跟踪,您会看到它在我上面列出的两个可能的代码路径中的第一个中被炸弹。也就是说,在System.Linq.Enumerable.FirstOfDefault方法体中。