diff --git a/Source/LibationAvalonia/ViewModels/MainVM.cs b/Source/LibationAvalonia/ViewModels/MainVM.cs index 35089bb0..9e6a602c 100644 --- a/Source/LibationAvalonia/ViewModels/MainVM.cs +++ b/Source/LibationAvalonia/ViewModels/MainVM.cs @@ -41,7 +41,7 @@ namespace LibationAvalonia.ViewModels { await Task.WhenAll( SetBackupCountsAsync(fullLibrary), - ProductsDisplay.UpdateGridAsync(fullLibrary)); + Task.Run(() => ProductsDisplay.UpdateGridAsync(fullLibrary))); } private static string menufyText(string header) => Configuration.IsMacOs ? header : $"_{header}"; diff --git a/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs index d8bfee95..47173b02 100644 --- a/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs @@ -11,6 +11,7 @@ using ReactiveUI; using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -102,16 +103,17 @@ namespace LibationAvalonia.ViewModels internal async Task BindToGridAsync(List dbBooks) { - GridEntries = new(SOURCE) { Filter = CollectionFilter }; + var sc = await Dispatcher.UIThread.InvokeAsync(() => AvaloniaSynchronizationContext.Current); + AvaloniaSynchronizationContext.SetSynchronizationContext(sc); var geList = await LibraryBookEntry.GetAllProductsAsync(dbBooks); - var seriesEntries = await SeriesEntry.GetAllSeriesEntriesAsync(dbBooks); //Create the filtered-in list before adding entries to avoid a refresh FilteredInGridEntries = geList.Union(seriesEntries.SelectMany(s => s.Children)).FilterEntries(FilterString); //Adding entries to the Source list will invoke CollectionFilter - SOURCE.AddRange(geList.Concat(seriesEntries).OrderDescending(new RowComparer(null))); + //Perform on UI thread for safety + await Dispatcher.UIThread.InvokeAsync(() => SOURCE.AddRange(geList.Concat(seriesEntries).OrderDescending(new RowComparer(null)))); //Add all children beneath their parent foreach (var series in seriesEntries) @@ -121,6 +123,10 @@ namespace LibationAvalonia.ViewModels SOURCE.Insert(++seriesIndex, child); } + // Adding SOURCE to the DataGridViewCollection after building the source + //Saves ~500 ms on a library of ~4500 books. + //Perform on UI thread for safety + await Dispatcher.UIThread.InvokeAsync(() => GridEntries = new(SOURCE) { Filter = CollectionFilter }); GridEntries.CollectionChanged += GridEntries_CollectionChanged; GridEntries_CollectionChanged(); } diff --git a/Source/LibationAvalonia/Views/MainWindow.axaml.cs b/Source/LibationAvalonia/Views/MainWindow.axaml.cs index 005f3654..a70755dc 100644 --- a/Source/LibationAvalonia/Views/MainWindow.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow.axaml.cs @@ -63,12 +63,14 @@ namespace LibationAvalonia.Views public async Task OnLibraryLoadedAsync(List initialLibrary) { + //Get the ViewModel before crossing the await boundary + var vm = ViewModel; if (QuickFilters.UseDefault) - await ViewModel.PerformFilter(QuickFilters.Filters.FirstOrDefault()); + await vm.PerformFilter(QuickFilters.Filters.FirstOrDefault()); await Task.WhenAll( - ViewModel.SetBackupCountsAsync(initialLibrary), - ViewModel.ProductsDisplay.BindToGridAsync(initialLibrary)); + vm.SetBackupCountsAsync(initialLibrary), + Task.Run(() => vm.ProductsDisplay.BindToGridAsync(initialLibrary))); } public void ProductsDisplay_LiberateClicked(object _, LibraryBook libraryBook) => ViewModel.LiberateClicked(libraryBook); diff --git a/Source/LibationFileManager/FilePathCache.cs b/Source/LibationFileManager/FilePathCache.cs index 96724d4a..469de1fd 100644 --- a/Source/LibationFileManager/FilePathCache.cs +++ b/Source/LibationFileManager/FilePathCache.cs @@ -55,8 +55,9 @@ namespace LibationFileManager { if (!File.Exists(matchingFiles[i].Path)) { + var entryToRemove = matchingFiles[i]; matchingFiles.RemoveAt(i); - cacheChanged |= Remove(matchingFiles[i]); + cacheChanged |= Remove(entryToRemove); } } if (cacheChanged) @@ -79,8 +80,9 @@ namespace LibationFileManager return matchingFiles[i].Path; else { + var entryToRemove = matchingFiles[i]; matchingFiles.RemoveAt(i); - cacheChanged |= Remove(matchingFiles[i]); + cacheChanged |= Remove(entryToRemove); } } return null; @@ -141,6 +143,7 @@ namespace LibationFileManager { [JsonProperty] private readonly ConcurrentDictionary> Dictionary = new(); + private static object lockObject = new(); public List GetIdEntries(string id) { @@ -164,7 +167,21 @@ namespace LibationFileManager } public bool Remove(string id, TEntry entry) - => Dictionary.TryGetValue(id, out List? entries) && entries.Remove(entry); + { + lock (lockObject) + { + if (Dictionary.TryGetValue(id, out List? entries)) + { + var removed = entries?.Remove(entry) ?? false; + if (removed && entries?.Count == 0) + { + Dictionary.Remove(id, out _); + } + return removed; + } + else return false; + } + } } } }