diff --git a/MapControl/Shared/MapImageLayer.cs b/MapControl/Shared/MapImageLayer.cs index 6e536d1b..4abd5762 100644 --- a/MapControl/Shared/MapImageLayer.cs +++ b/MapControl/Shared/MapImageLayer.cs @@ -171,12 +171,7 @@ namespace MapControl protected async Task UpdateImageAsync() { updateTimer.Stop(); - - if (cancellationTokenSource != null) - { - cancellationTokenSource.Cancel(); - cancellationTokenSource = null; - } + cancellationTokenSource?.Cancel(); if (ParentMap != null && ParentMap.ActualWidth > 0d && ParentMap.ActualHeight > 0d) { @@ -187,9 +182,12 @@ namespace MapControl var boundingBox = ParentMap.ViewRectToBoundingBox(new Rect(x, y, width, height)); - cancellationTokenSource = new CancellationTokenSource(); + ImageSource image; - var image = await GetImageAsync(boundingBox, loadingProgress, cancellationTokenSource.Token); + using (cancellationTokenSource = new CancellationTokenSource()) + { + image = await GetImageAsync(boundingBox, loadingProgress, cancellationTokenSource.Token); + } cancellationTokenSource = null; diff --git a/MapControl/Shared/MapTileLayerBase.cs b/MapControl/Shared/MapTileLayerBase.cs index 668ed113..fb329977 100644 --- a/MapControl/Shared/MapTileLayerBase.cs +++ b/MapControl/Shared/MapTileLayerBase.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Threading; using System.Threading.Tasks; #if WPF using System.Windows; @@ -60,6 +62,7 @@ namespace MapControl private readonly Progress loadingProgress; private readonly DispatcherTimer updateTimer; private ITileImageLoader tileImageLoader; + private CancellationTokenSource cancellationTokenSource; private MapBase parentMap; protected MapTileLayerBase() @@ -191,14 +194,24 @@ namespace MapControl protected bool IsBaseMapLayer => parentMap != null && parentMap.Children.Count > 0 && parentMap.Children[0] == this; - protected Task LoadTilesAsync(IEnumerable tiles, string cacheName) + protected async Task LoadTilesAsync(IEnumerable tiles, string cacheName) { - return TileImageLoader.LoadTilesAsync(tiles, TileSource, cacheName, loadingProgress); + cancellationTokenSource?.Cancel(); + + if (TileSource != null && tiles != null && tiles.Any(tile => tile.IsPending)) + { + using (cancellationTokenSource = new CancellationTokenSource()) + { + await TileImageLoader.LoadTilesAsync(tiles, TileSource, cacheName, loadingProgress, cancellationTokenSource.Token); + } + + cancellationTokenSource = null; + } } protected void CancelLoadTiles() { - TileImageLoader.CancelLoadTiles(); + cancellationTokenSource?.Cancel(); ClearValue(LoadingProgressProperty); } diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs index cf4cbae5..7a639f4e 100644 --- a/MapControl/Shared/TileImageLoader.cs +++ b/MapControl/Shared/TileImageLoader.cs @@ -9,7 +9,6 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using System.Threading; - #if WPF using System.Windows.Media; #elif UWP @@ -25,9 +24,7 @@ namespace MapControl /// public interface ITileImageLoader { - Task LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress); - - void CancelLoadTiles(); + Task LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress, CancellationToken cancellationToken); } public partial class TileImageLoader : ITileImageLoader @@ -73,76 +70,52 @@ namespace MapControl private static ILogger logger; private static ILogger Logger => logger ?? (logger = ImageLoader.LoggerFactory?.CreateLogger()); - private ConcurrentStack pendingTiles = new ConcurrentStack(); - - private CancellationTokenSource cancellationTokenSource; - - public void CancelLoadTiles() - { - pendingTiles.Clear(); - - if (cancellationTokenSource != null) - { - cancellationTokenSource.Cancel(); - cancellationTokenSource = null; - } - } - /// /// Loads all pending tiles from the tiles collection. Tile image caching is enabled when the Cache /// property is not null and tileSource.UriFormat starts with "http" and cacheName is a non-empty string. /// - public async Task LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress) + public async Task LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress, CancellationToken cancellationToken) { - CancelLoadTiles(); + var pendingTiles = new ConcurrentStack(tiles.Where(tile => tile.IsPending).Reverse()); + var tileCount = pendingTiles.Count; + var taskCount = Math.Min(tileCount, MaxLoadTasks); - if (tileSource != null && tiles != null && (tiles = tiles.Where(tile => tile.IsPending)).Any()) + if (taskCount > 0) { - pendingTiles = new ConcurrentStack(tiles.Reverse()); + progress?.Report(0d); - var tileCount = pendingTiles.Count; - var taskCount = Math.Min(tileCount, MaxLoadTasks); - - if (taskCount > 0) + if (Cache == null || tileSource.UriTemplate == null || !tileSource.UriTemplate.StartsWith("http")) { - if (Cache == null || tileSource.UriTemplate == null || !tileSource.UriTemplate.StartsWith("http")) + cacheName = null; // disable tile image caching + } + + async Task LoadTilesFromQueueAsync() + { + while (!cancellationToken.IsCancellationRequested && pendingTiles.TryPop(out var tile)) { - cacheName = null; // disable tile image caching - } + tile.IsPending = false; - progress?.Report(0d); + progress.Report((double)(tileCount - pendingTiles.Count) / tileCount); - var tasks = new Task[taskCount]; - var tileStack = pendingTiles; // pendingTiles member may change while tasks are running - - cancellationTokenSource = new CancellationTokenSource(); - - async Task LoadTilesFromQueueAsync() - { - while (tileStack.TryPop(out var tile)) // use captured tileStack variable in local function + try { - tile.IsPending = false; - - progress?.Report((double)(tileCount - tileStack.Count) / tileCount); - - try - { - await LoadTileImage(tile, tileSource, cacheName, cancellationTokenSource.Token).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger?.LogError(ex, "Failed loading tile image {zoom}/{column}/{row}", tile.ZoomLevel, tile.Column, tile.Row); - } + await LoadTileImage(tile, tileSource, cacheName, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger?.LogError(ex, "Failed loading tile image {zoom}/{column}/{row}", tile.ZoomLevel, tile.Column, tile.Row); } } - - for (int i = 0; i < taskCount; i++) - { - tasks[i] = Task.Run(LoadTilesFromQueueAsync); - } - - await Task.WhenAll(tasks); } + + var tasks = new Task[taskCount]; + + for (int i = 0; i < taskCount; i++) + { + tasks[i] = Task.Run(LoadTilesFromQueueAsync, cancellationToken); + } + + await Task.WhenAll(tasks); } }