diff --git a/MapControl/Shared/ImageLoader.cs b/MapControl/Shared/ImageLoader.cs index 4c7ffa02..ed307687 100644 --- a/MapControl/Shared/ImageLoader.cs +++ b/MapControl/Shared/ImageLoader.cs @@ -28,7 +28,7 @@ namespace MapControl static ImageLoader() { - HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) }; + HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }; HttpClient.DefaultRequestHeaders.Add("User-Agent", $"XAML-Map-Control/{typeof(ImageLoader).Assembly.GetName().Version}"); } @@ -126,13 +126,13 @@ namespace MapControl } catch (OperationCanceledException ex) { - if (ex.InnerException is TimeoutException timeout) + if (ex.CancellationToken.IsCancellationRequested) { - Logger?.LogError(timeout, "Failed loading image from {uri}", uri); + Logger?.LogTrace("Cancelled loading image from {uri}", uri); } else { - Logger?.LogTrace("Cancelled loading image from {uri}", uri); + Logger?.LogError(ex, "Failed loading image from {uri}", uri); } } catch (Exception ex) diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs index 955c8491..0e912e89 100644 --- a/MapControl/Shared/MapTileLayer.cs +++ b/MapControl/Shared/MapTileLayer.cs @@ -1,5 +1,4 @@ using System; -using System.Threading.Tasks; #if WPF using System.Windows; using System.Windows.Media; @@ -112,7 +111,7 @@ namespace MapControl return finalSize; } - protected override async Task UpdateTileLayerAsync(bool resetTiles) + protected override void UpdateTileLayerAsync(bool resetTiles) { if (ParentMap == null || ParentMap.MapProjection.Type != MapProjectionType.WebMercator) { @@ -131,8 +130,7 @@ namespace MapControl } UpdateTiles(); - - await LoadTilesAsync(Tiles, SourceName); + LoadTiles(Tiles, SourceName); } } diff --git a/MapControl/Shared/MapTileLayerBase.cs b/MapControl/Shared/MapTileLayerBase.cs index 40d5437c..fe1faa2a 100644 --- a/MapControl/Shared/MapTileLayerBase.cs +++ b/MapControl/Shared/MapTileLayerBase.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; #if WPF using System.Windows; using System.Windows.Controls; @@ -32,7 +30,7 @@ namespace MapControl { public static readonly DependencyProperty TileSourceProperty = DependencyPropertyHelper.Register(nameof(TileSource), null, - async (layer, oldValue, newValue) => await layer.UpdateTileLayer(true)); + (layer, oldValue, newValue) => layer.UpdateTileLayer(true)); public static readonly DependencyProperty SourceNameProperty = DependencyPropertyHelper.Register(nameof(SourceName)); @@ -61,7 +59,6 @@ namespace MapControl private readonly Progress loadingProgress; private readonly DispatcherTimer updateTimer; - private CancellationTokenSource cancellationTokenSource; private ITileImageLoader tileImageLoader; private MapBase parentMap; @@ -72,7 +69,7 @@ namespace MapControl loadingProgress = new Progress(p => SetValue(LoadingProgressProperty, p)); updateTimer = this.CreateTimer(UpdateInterval); - updateTimer.Tick += async (s, e) => await UpdateTileLayer(false); + updateTimer.Tick += (s, e) => UpdateTileLayer(false); MapPanel.SetRenderTransform(this, new MatrixTransform()); #if WPF @@ -194,44 +191,37 @@ namespace MapControl protected bool IsBaseMapLayer => parentMap != null && parentMap.Children.Count > 0 && parentMap.Children[0] == this; - protected async Task LoadTilesAsync(IEnumerable tiles, string cacheName) + protected void LoadTiles(IEnumerable tiles, string cacheName) { - 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; + TileImageLoader.LoadTiles(tiles, TileSource, cacheName, loadingProgress); } } protected void CancelLoadTiles() { - cancellationTokenSource?.Cancel(); + TileImageLoader.CancelLoadTiles(); ClearValue(LoadingProgressProperty); } protected abstract void SetRenderTransform(); - protected abstract Task UpdateTileLayerAsync(bool resetTiles); + protected abstract void UpdateTileLayerAsync(bool resetTiles); - private Task UpdateTileLayer(bool resetTiles) + private void UpdateTileLayer(bool resetTiles) { updateTimer.Stop(); - return UpdateTileLayerAsync(resetTiles); + UpdateTileLayerAsync(resetTiles); } - private async void OnViewportChanged(object sender, ViewportChangedEventArgs e) + private void OnViewportChanged(object sender, ViewportChangedEventArgs e) { if (e.TransformCenterChanged || e.ProjectionChanged || Children.Count == 0) { - await UpdateTileLayer(false); // update immediately + UpdateTileLayer(false); // update immediately } else { diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs index 68ce5676..b4270655 100644 --- a/MapControl/Shared/TileImageLoader.cs +++ b/MapControl/Shared/TileImageLoader.cs @@ -24,7 +24,9 @@ namespace MapControl /// public interface ITileImageLoader { - Task LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress, CancellationToken cancellationToken); + void LoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress); + + void CancelLoadTiles(); } public partial class TileImageLoader : ITileImageLoader @@ -70,56 +72,69 @@ namespace MapControl private static ILogger logger; private static ILogger Logger => logger ?? (logger = ImageLoader.LoggerFactory?.CreateLogger()); + private readonly ConcurrentStack tileStack = new ConcurrentStack(); + private int tileCount; + private int taskCount; + /// /// 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, CancellationToken cancellationToken) + public void LoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress) { - var pendingTiles = new ConcurrentStack(tiles.Where(tile => tile.IsPending).Reverse()); - 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 caching + } + + var currentTiles = tiles.Where(tile => tile.IsPending).Reverse().ToArray(); + + tileStack.Clear(); + tileStack.PushRange(currentTiles); + tileCount = currentTiles.Length; + + var maxTasks = Math.Min(tileCount, MaxLoadTasks); + + while (taskCount < maxTasks) + { + Interlocked.Increment(ref taskCount); + Logger?.LogTrace("Task count: {count}", taskCount); + + _ = Task.Run(async () => { - cacheName = null; // disable caching - } + await LoadTilesFromStack(tileSource, cacheName, progress).ConfigureAwait(false); - async Task LoadTilesFromQueue() - { - while (!cancellationToken.IsCancellationRequested && pendingTiles.TryPop(out var tile)) - { - tile.IsPending = false; + Interlocked.Decrement(ref taskCount); + Logger?.LogTrace("Task count: {count}", taskCount); + }); + } + } - progress?.Report((double)(tileCount - pendingTiles.Count) / tileCount); + public void CancelLoadTiles() + { + tileStack.Clear(); + } - Logger?.LogTrace("[{thread}] Loading {zoom}/{column}/{row}", Environment.CurrentManagedThreadId, tile.ZoomLevel, tile.Column, tile.Row); + private async Task LoadTilesFromStack(TileSource tileSource, string cacheName, IProgress progress) + { + while (tileStack.TryPop(out var tile)) + { + tile.IsPending = false; - try - { - await LoadTileImage(tile, tileSource, cacheName).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger?.LogError(ex, "Failed loading {zoom}/{column}/{row}", tile.ZoomLevel, tile.Column, tile.Row); - } - } - } + var tilesLoaded = tileCount - tileStack.Count; + + progress?.Report((double)tilesLoaded / tileCount); + + Logger?.LogTrace("[{thread}] Loading tile {loaded} of {count}: {zoom}/{column}/{row}", + Environment.CurrentManagedThreadId, tilesLoaded, tileCount, tile.ZoomLevel, tile.Column, tile.Row); try { - await Task.WhenAll(Enumerable.Range(0, taskCount).Select(_ => Task.Run(LoadTilesFromQueue, cancellationToken))); + await LoadTileImage(tile, tileSource, cacheName).ConfigureAwait(false); } - catch (OperationCanceledException) + catch (Exception ex) { - // no action - } - - if (cancellationToken.IsCancellationRequested) - { - Logger?.LogTrace("Cancelled LoadTilesAsync with {count} pending tiles", pendingTiles.Count); + Logger?.LogError(ex, "Failed loading tile {zoom}/{column}/{row}", tile.ZoomLevel, tile.Column, tile.Row); } } } diff --git a/MapControl/Shared/WmtsTileLayer.cs b/MapControl/Shared/WmtsTileLayer.cs index 6b7b2cc0..a1678883 100644 --- a/MapControl/Shared/WmtsTileLayer.cs +++ b/MapControl/Shared/WmtsTileLayer.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; #if WPF using System.Windows; #elif UWP @@ -92,7 +91,7 @@ namespace MapControl return finalSize; } - protected override async Task UpdateTileLayerAsync(bool resetTiles) + protected override void UpdateTileLayerAsync(bool resetTiles) { // resetTiles is ignored here because it is always false. @@ -121,7 +120,7 @@ namespace MapControl var tiles = ChildLayers.SelectMany(layer => layer.Tiles); - await LoadTilesAsync(tiles, cacheName); + LoadTiles(tiles, cacheName); } }