2016-07-06 2 views
2

J'espère que quelqu'un peut m'aider avec ce qui suit, parce que je suis complètement coincé."System.InvalidOperationException: Collection a été modifiée" dans MvvmCross TableView binding

Je reçois l'exception ci-dessous dans mon application MvvmCross Xamarin.iOS lorsque je lie sur mon TableView. Cela ne se produit que lorsque je change la source de données (chaque fois que je change la date, le TableView doit être mis à jour).

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 

Au début, je pensais qu'il était lié à l'un de mes méthodes Async (qui peut-être pas terminé dans le temps alors que le prochain était déjà en cours d'exécution). J'ai donc supprimé tout mon code asynchrone, mais l'exception se produit toujours. J'ai également veillé à ne pas modifier moi-même la collection énumérable. Je récupère les données (qui sont simplement un tableau dans la mémoire) et les renvoie en tant que nouvelle liste à la propriété à laquelle TableView est lié. Voici les extraits de code qui composent la liaison (il est beaucoup d'informations, mais je voulais être aussi complète que possible):

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

    } 
} 

CalendarViewModèle:

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 (implémente l'interface 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. 
     } 
} 

MISE À JOUR:

J'ai référencé mon projet directement aux assemblées de débogage de MvvmCross et compris que l'exception est levée sur la ligne 127 de la classe MvxTaskBasedBindingContext et arrive toujours à la deuxième itération. De ceci je conclus que la collection est changée pendant la première itération. Malheureusement, je ne peux pas comprendre pourquoi ou comment.

J'ai remarqué que le MvxTaskBasedBindingContext remplace le MvxBindingContext (modifié par softlion le 11-5-2016). Quand je force ma demande à utiliser la classe MvxBindingContext tout fonctionne bien (bien qu'un peu laggy). Cela me fait croire que le problème est dans le MvxTaskBasedBindingContext mais je ne peux vraiment pas comprendre pourquoi, toute aide serait grandement appréciée.

MISE À JOUR 2:

Après un peu plus de débogage et bidouiller j'ai découvert que l'exception est liée aux liaisons établies par ma CalendarTableViewCell classe (qui devrait fournir la mise en page pour chaque élément dans le tableview défini dans mon CalendarViewController Lorsque je commente les liaisons dans la classe CalendarTableViewCell l'exception ne se produit pas (voir mon code ci-dessus) Je ne sais toujours pas ce qui pourrait être faux cependant.

+0

Quelle version de MvvmCross? Je pense que quelque chose comme ça a déjà été résolu. – Cheesebaron

+0

Je rencontre ce problème dans les versions 4.2.0, 4.2.1 et 4.2.2. –

+1

Je ne connais pas trop iOS, mais une chose que j'ai remarquée est qu'ils ont tendance à utiliser DelayBind. Donc, peut-être envelopper votre liaison MvxTableViewCell dans un 'this.DelayBind (() => {/ * votre code pour la liaison dans LayoutSubviews ici * /});' peut aider? Pure deviner, je n'ai aucune idée comment iOS fonctionne lol. – Plac3Hold3r

Répondre

1

Vous pouvez utiliser DelayBind dans votre CalendarTableViewCell pour retarder la liaison jusqu'à ce que votre DataContext sur votre se prépare 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

Le problème ne sera pas fixe avec une liaison retard. Le problème est que les listes sont énumérées dans une tâche, qui peut être modifiée pendant l'énumération.

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

}); Avant d'énumérer besoin de créer une copie de la collection ToList() ou ToArray().

Ce bug a déjà été signalé. Link

+0

Je suis d'accord que le problème n'est pas résolu avec la liaison de retard. Cependant, il semble être une bonne solution pour le problème (jusqu'à ce que quelqu'un résout le problème réel, ce qui pourrait être moi). –