mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2025-12-06 07:12:04 +01:00
Image loading with cancellation
This commit is contained in:
parent
79b9e9d33d
commit
ebd90f5a45
|
|
@ -171,12 +171,7 @@ namespace MapControl
|
||||||
protected async Task UpdateImageAsync()
|
protected async Task UpdateImageAsync()
|
||||||
{
|
{
|
||||||
updateTimer.Stop();
|
updateTimer.Stop();
|
||||||
|
cancellationTokenSource?.Cancel();
|
||||||
if (cancellationTokenSource != null)
|
|
||||||
{
|
|
||||||
cancellationTokenSource.Cancel();
|
|
||||||
cancellationTokenSource = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ParentMap != null && ParentMap.ActualWidth > 0d && ParentMap.ActualHeight > 0d)
|
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));
|
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;
|
cancellationTokenSource = null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
#if WPF
|
#if WPF
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
@ -60,6 +62,7 @@ namespace MapControl
|
||||||
private readonly Progress<double> loadingProgress;
|
private readonly Progress<double> loadingProgress;
|
||||||
private readonly DispatcherTimer updateTimer;
|
private readonly DispatcherTimer updateTimer;
|
||||||
private ITileImageLoader tileImageLoader;
|
private ITileImageLoader tileImageLoader;
|
||||||
|
private CancellationTokenSource cancellationTokenSource;
|
||||||
private MapBase parentMap;
|
private MapBase parentMap;
|
||||||
|
|
||||||
protected MapTileLayerBase()
|
protected MapTileLayerBase()
|
||||||
|
|
@ -191,14 +194,24 @@ 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 Task LoadTilesAsync(IEnumerable<Tile> tiles, string cacheName)
|
protected async Task LoadTilesAsync(IEnumerable<Tile> 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()
|
protected void CancelLoadTiles()
|
||||||
{
|
{
|
||||||
TileImageLoader.CancelLoadTiles();
|
cancellationTokenSource?.Cancel();
|
||||||
|
|
||||||
ClearValue(LoadingProgressProperty);
|
ClearValue(LoadingProgressProperty);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
#if WPF
|
#if WPF
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
#elif UWP
|
#elif UWP
|
||||||
|
|
@ -25,9 +24,7 @@ namespace MapControl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITileImageLoader
|
public interface ITileImageLoader
|
||||||
{
|
{
|
||||||
Task LoadTilesAsync(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress);
|
Task LoadTilesAsync(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress, CancellationToken cancellationToken);
|
||||||
|
|
||||||
void CancelLoadTiles();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class TileImageLoader : ITileImageLoader
|
public partial class TileImageLoader : ITileImageLoader
|
||||||
|
|
@ -73,76 +70,52 @@ 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 ConcurrentStack<Tile> pendingTiles = new ConcurrentStack<Tile>();
|
|
||||||
|
|
||||||
private CancellationTokenSource cancellationTokenSource;
|
|
||||||
|
|
||||||
public void CancelLoadTiles()
|
|
||||||
{
|
|
||||||
pendingTiles.Clear();
|
|
||||||
|
|
||||||
if (cancellationTokenSource != null)
|
|
||||||
{
|
|
||||||
cancellationTokenSource.Cancel();
|
|
||||||
cancellationTokenSource = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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)
|
public async Task LoadTilesAsync(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CancelLoadTiles();
|
var pendingTiles = new ConcurrentStack<Tile>(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<Tile>(tiles.Reverse());
|
progress?.Report(0d);
|
||||||
|
|
||||||
var tileCount = pendingTiles.Count;
|
if (Cache == null || tileSource.UriTemplate == null || !tileSource.UriTemplate.StartsWith("http"))
|
||||||
var taskCount = Math.Min(tileCount, MaxLoadTasks);
|
|
||||||
|
|
||||||
if (taskCount > 0)
|
|
||||||
{
|
{
|
||||||
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];
|
try
|
||||||
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
|
|
||||||
{
|
{
|
||||||
tile.IsPending = false;
|
await LoadTileImage(tile, tileSource, cacheName, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
progress?.Report((double)(tileCount - tileStack.Count) / tileCount);
|
catch (Exception ex)
|
||||||
|
{
|
||||||
try
|
Logger?.LogError(ex, "Failed loading tile image {zoom}/{column}/{row}", tile.ZoomLevel, tile.Column, tile.Row);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue