2016-07-06 2 views
2

Надеюсь, кто-то может помочь мне в следующем: я полностью застрял.«System.InvalidOperationException: коллекция была изменена» в привязке таблицы MvvmCross TableView

Получаю исключение ниже в своем приложении MmvmCross Xamarin.iOS, когда привязываюсь к моему TableView. Это происходит только тогда, когда я изменяю источник данных (каждый раз, когда я меняю дату, TableView необходимо обновить).

Incident Identifier: 7E7C2B15-7CC4-4AE7-9891-C4FD82358009 
CrashReporter Key: 46CC21C0-DDE1-4313-9658-EC79D767939B 
Hardware Model:  iPhone7,2 
Process:   UurwerkiOS [4326] 
Path:   /var/containers/Bundle/Application/75969477-A516-44C3-A5A3-5B24DDDC89C8/UurwerkiOS.app/UurwerkiOS 
Identifier:  com.route2it.uurwerk 
Version:   1.0 (1.0.96) 
Code Type:  ARM-64 
Parent Process: ??? [1] 

Date/Time:  2016-07-04T13:16:38Z 
Launch Time:  2016-07-04T13:16:31Z 
OS Version:  iPhone OS 9.3.2 (13F69) 
Report Version: 104 

Exception Type: SIGABRT 
Exception Codes: #0 at 0x1816ac11c 
Crashed Thread: 5 

Application Specific Information: 
*** Terminating app due to uncaught exception 'System.AggregateException', reason: 'System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.InvalidOperationException: Collection was modified; enumeration operation may not execute. 
    at System.ThrowHelper.ThrowInvalidOperationException (ExceptionResource resource) <0x10044bec0 + 0x00024> in <filename unknown>:0 
    at System.Collections.Generic.List`1+Enumerator[T].MoveNextRare() <0x1003bf900 + 0x0002f> in <filename unknown>:0 
    at System.Collections.Generic.List`1+Enumerator[T].MoveNext() <0x1003bf830 + 0x0009f> in <filename unknown>:0 
    at MvvmCross.Binding.BindingContext.MvxTaskBasedBindingContext.<OnDataContextChange>b__20_0() <0x1007c1990 + 0x0023f> in <filename unknown>:0 
    at System.Threading.Tasks.Task.InnerInvoke() <0x10043f1f0 + 0x0005f> in <filename unknown>:0 
    at System.Threading.Tasks.Task.Execute() <0x10043ea20 + 0x00043> in <filename unknown>:0 
    --- End of inner exception stack trace --- 
---> (Inner Exception #0) System.InvalidOperationException: Collection was modified; enumeration operation may not execute. 
    at System.ThrowHelper.ThrowInvalidOperationException (ExceptionResource resource) <0x10044bec0 + 0x00024> in <filename unknown>:0 
    at System.Collections.Generic.List`1+Enumerator[T].MoveNextRare() <0x1003bf900 + 0x0002f> in <filename unknown>:0 
    at System.Collections.Generic.List`1+Enumerator[T].MoveNext() <0x1003bf830 + 0x0009f> in <filename unknown>:0 
    at MvvmCross.Binding.BindingContext.MvxTaskBasedBindingContext.<OnDataContextChange>b__20_0() <0x1007c1990 + 0x0023f> in <filename unknown>:0 
    at System.Threading.Tasks.Task.InnerInvoke() <0x10043f1f0 + 0x0005f> in <filename unknown>:0 
    at System.Threading.Tasks.Task.Execute() <0x10043ea20 + 0x00043> in <filename unknown>:0 

Сначала я думал, что это было связано с одним из моих методов Async (которые, возможно, не завершить во времени, а следующий уже был запущен). Поэтому я удалил весь мой асинхронный код, но исключение все еще происходит. Я также убедился, что сам не переделаю перечислимую коллекцию. Я беру данные (это просто массив в памяти) и возвращает его как новый список для свойства, которому привязан TableView. Ниже приведены фрагменты кода, которые составляют связывание (это очень много информации, но я хотел быть как можно более полно):

CalendarViewController:

public override void ViewDidLoad() 
{ 
     base.ViewDidLoad(); 

     if (NavigationController != null) 
       NavigationController.NavigationBarHidden = false; 

     InitCalendar(); 
     InitNavigationItem(); 
     InitTableView(); 

     ApplyConstraints(); 

     var shiftForDateTableViewSource = new MvxSimpleTableViewSource(_tableView, CalendarTableViewCell.Key, CalendarTableViewCell.Key); 
     shiftForDateTableViewSource.DeselectAutomatically = true; 
     _tableView.RowHeight = 45; 
     _tableView.Source = shiftForDateTableViewSource; 

     var set = this.CreateBindingSet<CalendarView, CalendarViewModel>(); 
     set.Bind(shiftForDateTableViewSource).To(vm => vm.ShiftsForSelectedDate); 
     set.Bind(shiftForDateTableViewSource).For(vm => vm.SelectionChangedCommand).To(vm => vm.ShiftSelectedCommand); 
     set.Apply(); 

     _tableView.ReloadData(); 
} 

private void InitTableView() 
{ 
     _tableView = new UITableView(); 
     _tableView.RegisterClassForCellReuse(typeof(UITableViewCell), CalendarTableViewCell.Key); 

     Add(_tableView); 
} 

CalendarTableViewCell:

public partial class CalendarTableViewCell : MvxTableViewCell 
{ 
    public static readonly NSString Key = new NSString("CalendarTableViewCell"); 
    public static readonly UINib Nib; 

    static CalendarTableViewCell() 
    { 
     Nib = UINib.FromName("CalendarTableViewCell", NSBundle.MainBundle); 
    } 

    protected CalendarTableViewCell(IntPtr handle) : base(handle) 
    { 

    } 

    public override void LayoutSubviews() 
    { 
     base.LayoutSubviews(); 

     var set = this.CreateBindingSet<CalendarTableViewCell, Shift>(); 
     set.Bind(StartTimeLabel).To(vm => vm.StartDate).WithConversion("StringFormat", "HH:mm"); 
     set.Bind(EndTimeLabel).To(vm => vm.EndDate).WithConversion("StringFormat", "HH:mm"); 
     set.Bind(ColorBarView).For("BackgroundColor").To(vm => vm.Color).WithConversion("RGB"); 
     set.Bind(TitleLabel).To(vm => vm).WithConversion("ConcatenatedEventTitle"); 
     set.Bind(LocationLabel).To(vm => vm.Location); 
     set.Apply(); 

    } 
} 

КалендарьViewModel:

public class CalendarViewModel 
     : MvxViewModel 
{ 
     private readonly IShiftService _shiftService; 

     public CalendarViewModel(IShiftService shiftService) 
     { 
       if (shiftService == null) 
         throw new ArgumentNullException(nameof(shiftService)); 

       _shiftService = shiftService; 
     } 

     public override void Start() 
     { 
       base.Start(); 

       Shifts = _shiftService.GetShiftsForEmployeeAsync(1); 
     } 

     private IEnumerable<Shift> _shifts; 
     public IEnumerable<Shift> Shifts 
     { 
       get { return _shifts; } 
       set 
       { 
         SetProperty(ref _shifts, 
               value, 
               nameof(Shifts)); 
       } 
     } 

     private IEnumerable<Shift> _shiftsForSelectedDate; 
     public IEnumerable<Shift> ShiftsForSelectedDate 
     { 
       get { return _shiftsForSelectedDate; } 
       private set 
       { 
         if (_shiftsForSelectedDate == value) 
           return; 

         SetProperty(ref _shiftsForSelectedDate, 
               value, 
               nameof(ShiftsForSelectedDate)); 
       } 
     } 

     private DateTime? _selectedDate; 
     public DateTime? SelectedDate 
     { 
       get { return _selectedDate; } 
       set 
       { 
         if (_selectedDate == value) 
           return; 

         SetProperty(ref _selectedDate, 
               value, 
               nameof(SelectedDate)); 

         if (_selectedDate.HasValue) 
           FetchShiftsForSelectedDate(); 
       } 
     } 

     private void FetchShiftsForSelectedDate() 
     { 
       ShiftsForSelectedDate = _shiftService.GetShiftsForSelectedDateAsync(_selectedDate.Value); 
     } 
} 

MockShiftService (реализует интерфейс IShiftService):

public class MockShiftService 
     : IShiftService 
{ 
     private IList<Shift> _shifts; 

     public MockShiftService() 
     { 
       Initialize(); 
     } 

     public IEnumerable<Shift> GetShiftsForEmployeeAsync(int employeeId) 
     { 
       return _shifts; 
     } 

     public IEnumerable<Shift> GetShiftsForSelectedDateAsync(DateTime selectedDate) 
     { 
       var endDate = selectedDate.Date.Add(new TimeSpan(23, 59, 59)); 

       return _shifts 
                 .Where(s => s.StartDate <= endDate && s.EndDate >= selectedDate) 
                 .ToList(); 
     } 

     public Shift GetShiftByIdAsync(int shiftId) 
     { 
       return _shifts.First((shift) => shift.Id == shiftId); 
     } 

     private void Initialize() 
     { 
       var shifts = new List<Shift>(); 

       // The in memory array gets populated here which 
       // is straight forward creating instances of the 
       // 'Shift' class and assigning it's properties before 
       // adding it to the 'shifts' collection. I left 
       // this code out to keep it as short as possible. 
     } 
} 

UPDATE:

Я ссылается мой проект непосредственно отладочных сборок MvvmCross и понял, что исключение на линии 127 класса MvxTaskBasedBindingContext и всегда происходит на второй итерации. Из этого я делаю вывод, что коллекция меняется во время первой итерации. К сожалению, я не могу понять, почему и как.

Я заметил, что MvxTaskBasedBindingContext заменяет MvxBindingContext (изменен softlion на 11-5-2016). Когда я применяю приложение для использования класса MvxBindingContext, все работает хорошо (хотя немного лагги). Это заставляет меня поверить, что проблема в MvxTaskBasedBindingContext, но я действительно не могу понять, почему любая помощь будет принята с благодарностью.

UPDATE 2:

После некоторых более отладки и пустячный вокруг я узнал, что исключение связано с креплениями, установленными моим CalendarTableViewCell класса (который должен предоставить макет для каждого элемента в Tableview, определенной в my CalendarViewController. Когда я комментирую привязки в классе CalendarTableViewCell, исключение не возникает (см. мой код выше). Я все еще не знаю, что может быть неправильным.

+0

Какая версия MvvmCross? Я думаю, что что-то подобное уже было рассмотрено. – Cheesebaron

+0

Я испытываю эту проблему в версиях 4.2.0, 4.2.1 и 4.2.2. –

+1

Я не слишком знаком с iOS, но я заметил, что они обычно используют «DelayBind». Поэтому, возможно, обертывание вашей привязки MvxTableViewCell в 'this.DelayBind (() => {/ * ваш код для привязки в LayoutSubviews здесь * /});' может помочь? Чистое угадывание, я понятия не имею, как работает iOS lols. – Plac3Hold3r

ответ

1

Вы можете использовать DelayBind в вашем CalendarTableViewCell задержать связывание, пока ваш DataContext не получает набор на вашем BindingContext

public partial class CalendarTableViewCell : MvxTableViewCell 
{ 
    ... 

    public override void LayoutSubviews() 
    { 
     base.LayoutSubviews(); 
     this.DelayBind(() => 
     { 
      var set = this.CreateBindingSet<CalendarTableViewCell, Shift>(); 
      set.Bind(StartTimeLabel).To(vm => vm.StartDate).WithConversion("StringFormat", "HH:mm"); 
      set.Bind(EndTimeLabel).To(vm => vm.EndDate).WithConversion("StringFormat", "HH:mm"); 
      set.Bind(ColorBarView).For("BackgroundColor").To(vm => vm.Color).WithConversion("RGB"); 
      set.Bind(TitleLabel).To(vm => vm).WithConversion("ConcatenatedEventTitle"); 
      set.Bind(LocationLabel).To(vm => vm.Location); 
      set.Apply(); 
     }); 
    } 
} 
1

Проблема не будет решена с привязкой задержки. Проблема состоит в том, что Списки перечислены в Задаче, которая может быть изменена, когда происходит перечисление.

Task.Run(() => 
{ 
    foreach (var binding in this._viewBindings) 
    { 
     foreach (var bind in binding.Value) 
     { 
      bind.Binding.DataContext = this._dataContext; 
     } 
    } 

    foreach (var binding in this._directBindings) 
    { 
     binding.Binding.DataContext = this._dataContext; 
    } 

});

Перед перечислением необходимо создать копию коллекции ToList() или ToArray().

Об этой ошибке уже сообщалось. Link

+0

Я согласен, что проблема не связана с привязкой к задержке. Однако, похоже, это хорошая проблема для решения проблемы (пока кто-то не решает проблему, которая может быть мной). –