From b8047eadd158dcbc776d889744331f02422382dd Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Thu, 12 Jun 2025 16:00:24 -0700 Subject: [PATCH 01/10] Fixing BasemapGallery Sceneview appears blank sometimes. Refactor NotifySpatialReferenceChanged method calls for improved asynchronous handling. --- .../UI/Controls/BasemapGallery/BasemapGalleryController.cs | 6 +++--- .../UI/Controls/BasemapGallery/BasemapGalleryItem.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index fb30d1b75..7a471e166 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -148,7 +148,7 @@ private void HandleAvailableBasemapsChanged() PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AvailableBasemaps))); // Update validity - AvailableBasemaps?.ToList()?.ForEach(bmgi => bmgi.NotifySpatialReferenceChanged(GeoModel)); + AvailableBasemaps?.ToList()?.ForEach(bmgi => _ = bmgi.NotifySpatialReferenceChanged(GeoModel)); // Update selection. UpdateSelectionForGeoModelBasemap(); @@ -168,7 +168,7 @@ private void HandleAvailableBasemapsCollectionChanged(object? sender, NotifyColl case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Reset: - e.NewItems?.OfType().ToList().ForEach(bmgi => bmgi.NotifySpatialReferenceChanged(GeoModel)); + e.NewItems?.OfType().ToList().ForEach(bmgi => _ = bmgi.NotifySpatialReferenceChanged(GeoModel)); UpdateSelectionForGeoModelBasemap(); break; case NotifyCollectionChangedAction.Remove: @@ -187,7 +187,7 @@ private void HandleGeoModelPropertyChanged(object? sender, PropertyChangedEventA } else if (e.PropertyName == nameof(GeoModel.SpatialReference)) { - AvailableBasemaps?.ToList().ForEach(item => item.NotifySpatialReferenceChanged(GeoModel)); + AvailableBasemaps?.ToList().ForEach(item => _ = item.NotifySpatialReferenceChanged(GeoModel)); } } diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs index db672ea6d..f24947b21 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs @@ -135,7 +135,7 @@ private async Task LoadImage() } } - internal async void NotifySpatialReferenceChanged(GeoModel? gm) + internal async Task NotifySpatialReferenceChanged(GeoModel? gm) { try { From edf8b23e1588d65ae015ebe6dfd5415540cd7d11 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Fri, 13 Jun 2025 13:06:14 -0700 Subject: [PATCH 02/10] Using Async Await in foreach block --- .../UI/Controls/BasemapGallery/BasemapGalleryController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index 7a471e166..e80e16f8e 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -148,7 +148,7 @@ private void HandleAvailableBasemapsChanged() PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AvailableBasemaps))); // Update validity - AvailableBasemaps?.ToList()?.ForEach(bmgi => _ = bmgi.NotifySpatialReferenceChanged(GeoModel)); + AvailableBasemaps?.ToList()?.ForEach(async bmgi => await bmgi.NotifySpatialReferenceChanged(GeoModel)); // Update selection. UpdateSelectionForGeoModelBasemap(); @@ -168,7 +168,7 @@ private void HandleAvailableBasemapsCollectionChanged(object? sender, NotifyColl case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Reset: - e.NewItems?.OfType().ToList().ForEach(bmgi => _ = bmgi.NotifySpatialReferenceChanged(GeoModel)); + e.NewItems?.OfType().ToList().ForEach(async bmgi => await bmgi.NotifySpatialReferenceChanged(GeoModel)); UpdateSelectionForGeoModelBasemap(); break; case NotifyCollectionChangedAction.Remove: @@ -187,7 +187,7 @@ private void HandleGeoModelPropertyChanged(object? sender, PropertyChangedEventA } else if (e.PropertyName == nameof(GeoModel.SpatialReference)) { - AvailableBasemaps?.ToList().ForEach(item => _ = item.NotifySpatialReferenceChanged(GeoModel)); + AvailableBasemaps?.ToList().ForEach(async item => await item.NotifySpatialReferenceChanged(GeoModel)); } } From 795bbe485867e6604421351bfcc9c4058bed74c9 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Fri, 13 Jun 2025 15:34:46 -0700 Subject: [PATCH 03/10] Introduced a semaphore for thread-safe access to UpdateBasemaps. This avoids multiple invocations of UpdateBasemaps() and NotifySpatialReferenceChanged() --- .../BasemapGallery/BasemapGalleryController.cs | 16 +++++++++++++--- .../BasemapGallery/BasemapGalleryItem.cs | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index e80e16f8e..785e0651a 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -148,7 +148,7 @@ private void HandleAvailableBasemapsChanged() PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AvailableBasemaps))); // Update validity - AvailableBasemaps?.ToList()?.ForEach(async bmgi => await bmgi.NotifySpatialReferenceChanged(GeoModel)); + AvailableBasemaps?.ToList()?.ForEach(bmgi => bmgi.NotifySpatialReferenceChanged(GeoModel)); // Update selection. UpdateSelectionForGeoModelBasemap(); @@ -168,7 +168,7 @@ private void HandleAvailableBasemapsCollectionChanged(object? sender, NotifyColl case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Reset: - e.NewItems?.OfType().ToList().ForEach(async bmgi => await bmgi.NotifySpatialReferenceChanged(GeoModel)); + e.NewItems?.OfType().ToList().ForEach(bmgi => bmgi.NotifySpatialReferenceChanged(GeoModel)); UpdateSelectionForGeoModelBasemap(); break; case NotifyCollectionChangedAction.Remove: @@ -187,7 +187,7 @@ private void HandleGeoModelPropertyChanged(object? sender, PropertyChangedEventA } else if (e.PropertyName == nameof(GeoModel.SpatialReference)) { - AvailableBasemaps?.ToList().ForEach(async item => await item.NotifySpatialReferenceChanged(GeoModel)); + AvailableBasemaps?.ToList().ForEach(item => item.NotifySpatialReferenceChanged(GeoModel)); } } @@ -197,8 +197,13 @@ private async Task HandlePortalChanged() await UpdateBasemaps(); } + private readonly SemaphoreSlim _updateBasemapsSemaphore = new(1, 1); + public async Task UpdateBasemaps() { + await _updateBasemapsSemaphore.WaitAsync(); + try + { IsLoading = true; // Cancel any pending load before starting a new one _loadCancellationTokenSource?.Cancel(); @@ -217,6 +222,11 @@ public async Task UpdateBasemaps() IsLoading = false; } } + finally + { + _updateBasemapsSemaphore.Release(); + } + } private void HandleSelectedBasemapChanged() { diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs index f24947b21..db672ea6d 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs @@ -135,7 +135,7 @@ private async Task LoadImage() } } - internal async Task NotifySpatialReferenceChanged(GeoModel? gm) + internal async void NotifySpatialReferenceChanged(GeoModel? gm) { try { From b50226180a03ad5ab29c82ae72dbf2367a5f6052 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Tue, 17 Jun 2025 18:10:41 -0700 Subject: [PATCH 04/10] Refactor UpdateBasemaps for better error handling Moved Cancellation operation to get semaphopre release earlier --- .../BasemapGalleryController.cs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index 785e0651a..2264ade72 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -201,27 +201,29 @@ private async Task HandlePortalChanged() public async Task UpdateBasemaps() { - await _updateBasemapsSemaphore.WaitAsync(); - try - { - IsLoading = true; // Cancel any pending load before starting a new one _loadCancellationTokenSource?.Cancel(); - _loadCancellationTokenSource = new CancellationTokenSource(); + + await _updateBasemapsSemaphore.WaitAsync(); try { - _availableBasemaps = await PopulateBasemaps(_loadCancellationTokenSource.Token); - HandleAvailableBasemapsChanged(); - } - catch (Exception ex) - { - System.Diagnostics.Trace.WriteLine(ex.Message); - } - finally - { - IsLoading = false; + IsLoading = true; + _loadCancellationTokenSource = new CancellationTokenSource(); + try + { + _availableBasemaps = await PopulateBasemaps(_loadCancellationTokenSource.Token); + HandleAvailableBasemapsChanged(); + } + catch (OperationCanceledException) { } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(ex.Message); + } + finally + { + IsLoading = false; + } } - } finally { _updateBasemapsSemaphore.Release(); From a6b71c2b891dee9139750eae4e974bc7371144e1 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Fri, 20 Jun 2025 10:21:33 -0700 Subject: [PATCH 05/10] Modified `UpdateBasemaps` to attempt semaphore entry without waiting. If another update is in progress, the method exits immediately. --- .../BasemapGallery/BasemapGalleryController.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index 2264ade72..710b392b7 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -201,12 +201,15 @@ private async Task HandlePortalChanged() public async Task UpdateBasemaps() { - // Cancel any pending load before starting a new one - _loadCancellationTokenSource?.Cancel(); - - await _updateBasemapsSemaphore.WaitAsync(); + // Try to enter the semaphore without waiting + if (!await _updateBasemapsSemaphore.WaitAsync(0)) + { + // Another update is already running; exit immediately + return; + } try { + _loadCancellationTokenSource?.Cancel(); IsLoading = true; _loadCancellationTokenSource = new CancellationTokenSource(); try From 5db8985610219a9f993fa9fcd2d693569cf0ddec Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Fri, 20 Jun 2025 10:21:57 -0700 Subject: [PATCH 06/10] Adding missing space --- .../UI/Controls/BasemapGallery/BasemapGalleryController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index 710b392b7..486f26516 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -209,7 +209,7 @@ public async Task UpdateBasemaps() } try { - _loadCancellationTokenSource?.Cancel(); + _loadCancellationTokenSource?.Cancel(); IsLoading = true; _loadCancellationTokenSource = new CancellationTokenSource(); try From 861dcdffd99e4d499cb164a854057e070a2e70d0 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Fri, 20 Jun 2025 10:28:32 -0700 Subject: [PATCH 07/10] Removing Redundant code --- .../Controls/BasemapGallery/BasemapGalleryController.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index 486f26516..86c47e1d3 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -301,13 +301,7 @@ private static bool BasemapIsActuallyNotABasemap(Basemap input) private Task> LoadBasemapGalleryItems(ArcGISPortal portal, CancellationToken cancellationToken = default) { - if (_loadBasemapGalleryItemsTask is null || _loadBasemapGalleryItemsTask.IsCompleted) - { - _loadBasemapGalleryItemsTask = LoadBasemapGalleryItemsInternal(portal, cancellationToken); - return _loadBasemapGalleryItemsTask; - } - - return _loadBasemapGalleryItemsTask; + return LoadBasemapGalleryItemsInternal(portal, cancellationToken); } private async Task> LoadBasemapGalleryItemsInternal(ArcGISPortal portal, CancellationToken cancellationToken = default) From d2f88b9624c2bd0532d83f9668aa250c58f4cab6 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Fri, 20 Jun 2025 10:30:38 -0700 Subject: [PATCH 08/10] Removing redundant method nesting --- .../UI/Controls/BasemapGallery/BasemapGalleryController.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index 86c47e1d3..e3ad40ca1 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -299,12 +299,7 @@ private static bool BasemapIsActuallyNotABasemap(Basemap input) return await LoadBasemapGalleryItems(Portal, cancellationToken); } - private Task> LoadBasemapGalleryItems(ArcGISPortal portal, CancellationToken cancellationToken = default) - { - return LoadBasemapGalleryItemsInternal(portal, cancellationToken); - } - - private async Task> LoadBasemapGalleryItemsInternal(ArcGISPortal portal, CancellationToken cancellationToken = default) + private async Task> LoadBasemapGalleryItems(ArcGISPortal portal, CancellationToken cancellationToken = default) { async Task> LoadBasemapsAsync(Func>> getBasemapsFunc) { From ef2b614762ef599a8cc055e309b4be1e0cec3710 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Fri, 27 Jun 2025 18:41:29 -0700 Subject: [PATCH 09/10] Moving logic away from SemaphoreSlim and using pending boolean locks to make sure collate subsequent update calls and make it a single call. --- .../BasemapGalleryController.cs | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index e3ad40ca1..9e3955c2d 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -197,39 +197,44 @@ private async Task HandlePortalChanged() await UpdateBasemaps(); } - private readonly SemaphoreSlim _updateBasemapsSemaphore = new(1, 1); + private bool _pendingUpdateBasemaps; + private bool _isUpdatingBasemaps; public async Task UpdateBasemaps() { - // Try to enter the semaphore without waiting - if (!await _updateBasemapsSemaphore.WaitAsync(0)) - { - // Another update is already running; exit immediately + _pendingUpdateBasemaps = true; + if (_isUpdatingBasemaps) return; - } + + _isUpdatingBasemaps = true; try { - _loadCancellationTokenSource?.Cancel(); - IsLoading = true; - _loadCancellationTokenSource = new CancellationTokenSource(); - try - { - _availableBasemaps = await PopulateBasemaps(_loadCancellationTokenSource.Token); - HandleAvailableBasemapsChanged(); - } - catch (OperationCanceledException) { } - catch (Exception ex) - { - System.Diagnostics.Trace.WriteLine(ex.Message); - } - finally + while (_pendingUpdateBasemaps) { - IsLoading = false; + _pendingUpdateBasemaps = false; + + _loadCancellationTokenSource?.Cancel(); + IsLoading = true; + _loadCancellationTokenSource = new CancellationTokenSource(); + try + { + _availableBasemaps = await PopulateBasemaps(_loadCancellationTokenSource.Token); + HandleAvailableBasemapsChanged(); + } + catch (OperationCanceledException) { } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(ex.Message); + } + finally + { + IsLoading = false; + } } } finally { - _updateBasemapsSemaphore.Release(); + _isUpdatingBasemaps = false; } } From 452a3aa96f3bbaf9297f31aa0deae955068ea3e9 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Mon, 30 Jun 2025 16:35:51 -0700 Subject: [PATCH 10/10] Removed the _isUpdatingBasemaps variable and replaced it with the IsLoading property to manage the loading state. This change simplifies and consolidating it into a single property. --- .../BasemapGallery/BasemapGalleryController.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index 9e3955c2d..6129a0d8d 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -198,15 +198,14 @@ private async Task HandlePortalChanged() } private bool _pendingUpdateBasemaps; - private bool _isUpdatingBasemaps; public async Task UpdateBasemaps() { _pendingUpdateBasemaps = true; - if (_isUpdatingBasemaps) + if (IsLoading) return; - _isUpdatingBasemaps = true; + IsLoading = true; try { while (_pendingUpdateBasemaps) @@ -214,7 +213,6 @@ public async Task UpdateBasemaps() _pendingUpdateBasemaps = false; _loadCancellationTokenSource?.Cancel(); - IsLoading = true; _loadCancellationTokenSource = new CancellationTokenSource(); try { @@ -226,15 +224,11 @@ public async Task UpdateBasemaps() { System.Diagnostics.Trace.WriteLine(ex.Message); } - finally - { - IsLoading = false; - } } } finally { - _isUpdatingBasemaps = false; + IsLoading = false; } }