Reverted to not awaitable TileImageLoader

This commit is contained in:
ClemensFischer 2025-08-21 21:25:04 +02:00
parent 775d584df7
commit 178cec8e66
5 changed files with 68 additions and 66 deletions

View file

@ -28,7 +28,7 @@ namespace MapControl
static ImageLoader() 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}"); HttpClient.DefaultRequestHeaders.Add("User-Agent", $"XAML-Map-Control/{typeof(ImageLoader).Assembly.GetName().Version}");
} }
@ -126,13 +126,13 @@ namespace MapControl
} }
catch (OperationCanceledException ex) 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 else
{ {
Logger?.LogTrace("Cancelled loading image from {uri}", uri); Logger?.LogError(ex, "Failed loading image from {uri}", uri);
} }
} }
catch (Exception ex) catch (Exception ex)

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Threading.Tasks;
#if WPF #if WPF
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
@ -112,7 +111,7 @@ namespace MapControl
return finalSize; return finalSize;
} }
protected override async Task UpdateTileLayerAsync(bool resetTiles) protected override void UpdateTileLayerAsync(bool resetTiles)
{ {
if (ParentMap == null || ParentMap.MapProjection.Type != MapProjectionType.WebMercator) if (ParentMap == null || ParentMap.MapProjection.Type != MapProjectionType.WebMercator)
{ {
@ -131,8 +130,7 @@ namespace MapControl
} }
UpdateTiles(); UpdateTiles();
LoadTiles(Tiles, SourceName);
await LoadTilesAsync(Tiles, SourceName);
} }
} }

View file

@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
#if WPF #if WPF
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@ -32,7 +30,7 @@ namespace MapControl
{ {
public static readonly DependencyProperty TileSourceProperty = public static readonly DependencyProperty TileSourceProperty =
DependencyPropertyHelper.Register<MapTileLayerBase, TileSource>(nameof(TileSource), null, DependencyPropertyHelper.Register<MapTileLayerBase, TileSource>(nameof(TileSource), null,
async (layer, oldValue, newValue) => await layer.UpdateTileLayer(true)); (layer, oldValue, newValue) => layer.UpdateTileLayer(true));
public static readonly DependencyProperty SourceNameProperty = public static readonly DependencyProperty SourceNameProperty =
DependencyPropertyHelper.Register<MapTileLayerBase, string>(nameof(SourceName)); DependencyPropertyHelper.Register<MapTileLayerBase, string>(nameof(SourceName));
@ -61,7 +59,6 @@ namespace MapControl
private readonly Progress<double> loadingProgress; private readonly Progress<double> loadingProgress;
private readonly DispatcherTimer updateTimer; private readonly DispatcherTimer updateTimer;
private CancellationTokenSource cancellationTokenSource;
private ITileImageLoader tileImageLoader; private ITileImageLoader tileImageLoader;
private MapBase parentMap; private MapBase parentMap;
@ -72,7 +69,7 @@ namespace MapControl
loadingProgress = new Progress<double>(p => SetValue(LoadingProgressProperty, p)); loadingProgress = new Progress<double>(p => SetValue(LoadingProgressProperty, p));
updateTimer = this.CreateTimer(UpdateInterval); updateTimer = this.CreateTimer(UpdateInterval);
updateTimer.Tick += async (s, e) => await UpdateTileLayer(false); updateTimer.Tick += (s, e) => UpdateTileLayer(false);
MapPanel.SetRenderTransform(this, new MatrixTransform()); MapPanel.SetRenderTransform(this, new MatrixTransform());
#if WPF #if WPF
@ -194,44 +191,37 @@ namespace MapControl
protected bool IsBaseMapLayer => parentMap != null && parentMap.Children.Count > 0 && parentMap.Children[0] == this; protected bool IsBaseMapLayer => parentMap != null && parentMap.Children.Count > 0 && parentMap.Children[0] == this;
protected async Task LoadTilesAsync(IEnumerable<Tile> tiles, string cacheName) protected void LoadTiles(IEnumerable<Tile> tiles, string cacheName)
{ {
cancellationTokenSource?.Cancel();
if (TileSource != null && tiles != null && tiles.Any(tile => tile.IsPending)) if (TileSource != null && tiles != null && tiles.Any(tile => tile.IsPending))
{ {
using (cancellationTokenSource = new CancellationTokenSource()) TileImageLoader.LoadTiles(tiles, TileSource, cacheName, loadingProgress);
{
await TileImageLoader.LoadTilesAsync(tiles, TileSource, cacheName, loadingProgress, cancellationTokenSource.Token);
}
cancellationTokenSource = null;
} }
} }
protected void CancelLoadTiles() protected void CancelLoadTiles()
{ {
cancellationTokenSource?.Cancel(); TileImageLoader.CancelLoadTiles();
ClearValue(LoadingProgressProperty); ClearValue(LoadingProgressProperty);
} }
protected abstract void SetRenderTransform(); 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(); 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) if (e.TransformCenterChanged || e.ProjectionChanged || Children.Count == 0)
{ {
await UpdateTileLayer(false); // update immediately UpdateTileLayer(false); // update immediately
} }
else else
{ {

View file

@ -24,7 +24,9 @@ namespace MapControl
/// </summary> /// </summary>
public interface ITileImageLoader public interface ITileImageLoader
{ {
Task LoadTilesAsync(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress, CancellationToken cancellationToken); void LoadTiles(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress);
void CancelLoadTiles();
} }
public partial class TileImageLoader : ITileImageLoader public partial class TileImageLoader : ITileImageLoader
@ -70,56 +72,69 @@ namespace MapControl
private static ILogger logger; private static ILogger logger;
private static ILogger Logger => logger ?? (logger = ImageLoader.LoggerFactory?.CreateLogger<TileImageLoader>()); private static ILogger Logger => logger ?? (logger = ImageLoader.LoggerFactory?.CreateLogger<TileImageLoader>());
private readonly ConcurrentStack<Tile> tileStack = new ConcurrentStack<Tile>();
private int tileCount;
private int taskCount;
/// <summary> /// <summary>
/// Loads all pending tiles from the tiles collection. Tile image caching is enabled when the Cache /// 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. /// property is not null and tileSource.UriFormat starts with "http" and cacheName is a non-empty string.
/// </summary> /// </summary>
public async Task LoadTilesAsync(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress, CancellationToken cancellationToken) public void LoadTiles(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress)
{ {
var pendingTiles = new ConcurrentStack<Tile>(tiles.Where(tile => tile.IsPending).Reverse()); if (Cache == null || tileSource.UriTemplate == null || !tileSource.UriTemplate.StartsWith("http"))
var tileCount = pendingTiles.Count;
var taskCount = Math.Min(tileCount, MaxLoadTasks);
if (taskCount > 0)
{ {
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() Interlocked.Decrement(ref taskCount);
{ Logger?.LogTrace("Task count: {count}", taskCount);
while (!cancellationToken.IsCancellationRequested && pendingTiles.TryPop(out var tile)) });
{ }
tile.IsPending = false; }
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<double> progress)
{
while (tileStack.TryPop(out var tile))
{
tile.IsPending = false;
try var tilesLoaded = tileCount - tileStack.Count;
{
await LoadTileImage(tile, tileSource, cacheName).ConfigureAwait(false); progress?.Report((double)tilesLoaded / tileCount);
}
catch (Exception ex) Logger?.LogTrace("[{thread}] Loading tile {loaded} of {count}: {zoom}/{column}/{row}",
{ Environment.CurrentManagedThreadId, tilesLoaded, tileCount, tile.ZoomLevel, tile.Column, tile.Row);
Logger?.LogError(ex, "Failed loading {zoom}/{column}/{row}", tile.ZoomLevel, tile.Column, tile.Row);
}
}
}
try 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 Logger?.LogError(ex, "Failed loading tile {zoom}/{column}/{row}", tile.ZoomLevel, tile.Column, tile.Row);
}
if (cancellationToken.IsCancellationRequested)
{
Logger?.LogTrace("Cancelled LoadTilesAsync with {count} pending tiles", pendingTiles.Count);
} }
} }
} }

View file

@ -2,7 +2,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
#if WPF #if WPF
using System.Windows; using System.Windows;
#elif UWP #elif UWP
@ -92,7 +91,7 @@ namespace MapControl
return finalSize; return finalSize;
} }
protected override async Task UpdateTileLayerAsync(bool resetTiles) protected override void UpdateTileLayerAsync(bool resetTiles)
{ {
// resetTiles is ignored here because it is always false. // resetTiles is ignored here because it is always false.
@ -121,7 +120,7 @@ namespace MapControl
var tiles = ChildLayers.SelectMany(layer => layer.Tiles); var tiles = ChildLayers.SelectMany(layer => layer.Tiles);
await LoadTilesAsync(tiles, cacheName); LoadTiles(tiles, cacheName);
} }
} }