Using ArcGIS Maps SDK for .NET 200.5, I am having some issues with IdentifyLayersAsync and cancellation token.
I am working on a map with a few clustered feature layers, and at certain map scales the Identify operation will be very slow when identifying the clusters. This is particularly true when having just navigated the map before performing the identify operation, while DrawState is InProgress.
It appears that performing multiple sequential calls to this method are queued if user taps the view several times in a row and the previous Identify has not been completed. What happens next is that first call is blocking the next call, and the time the user has to wait increases for every new identify request.
I've used CancellationTokenSource and pass the token to the Identify method. When cancelling the token, the IdentifyLayersAsync does not appear to actually be cancelled until the last request has finished.
Looking at the source for GeoView, the request _coreReference.IdentifyLayers method does not use the cancellation token, but uses CoreTaskExtensions.AsTask to try to perform some cancellation that seems ineffective. As far as I can tell, Cancel registration does not work, is not called or doesn't end the native call.
I can see that AsTask is used in multiple locations of the API, and if it doesn't do the trick here, I'm guessing the same might be the case in other locations (assuming I've not missed something critical in my own code).
private async Task<IReadOnlyList<IdentifyLayerResult>> IdentifyLayersInternal(
Point screenPoint,
double tolerance,
bool returnPopupsOnly,
long maximumResultsPerLayer,
CancellationToken cancellationToken)
{
return (IReadOnlyList<IdentifyLayerResult>) new IdentifyLayerResultList(await this._coreReference.IdentifyLayers(this.TransformToDrawSurface(screenPoint), tolerance, returnPopupsOnly, (int) maximumResultsPerLayer).AsTask<CoreArray>(cancellationToken).ConfigureAwait(false));
}
This is the code to reproduce.
private int _identifyCounter;
private async void MapView_MapViewTapped(object sender, GeoViewInputEventArgs e)
{
var currentCounter = Interlocked.Increment(ref _identifyCounter);
if (_cancellationTokenSource != null)
{
await _cancellationTokenSource.CancelAsync();
_cancellationTokenSource.Dispose();
_cancellationTokenSource = null;
Trace.WriteLine($"Previous token CancelAsync", $"Tapped {currentCounter}");
}
CancellationTokenSource source = _cancellationTokenSource = new CancellationTokenSource();
var identifyWatch = Stopwatch.StartNew();
try
{
Trace.WriteLine("Start identify", $"OnIdentify {currentCounter}");
// Used when mapscale is less than 1 : 100.000
var maxResultPerLayer = 12;
var tolerance = 15;
await MapView.IdentifyGraphicsOverlaysAsync(e.Position, tolerance, false, maxResultPerLayer);
await MapView.IdentifyLayersAsync(e.Position, tolerance, returnPopupsOnly: true, maxResultPerLayer, source.Token);
identifyWatch.Stop();
}
catch (OperationCanceledException)
{
identifyWatch.Stop();
Trace.WriteLine($"OperationCanceledException after {identifyWatch.Elapsed}", $"Tapped {currentCounter}");
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}
finally
{
if(!source.IsCancellationRequested)
{
Trace.WriteLine($"Finished after {identifyWatch.Elapsed}", $"Tapped {currentCounter}");
}
Interlocked.Decrement(ref _identifyCounter);
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
source?.Dispose();
}
}
The trace I get here is as follows:
Tapped 1: Finished after 00:00:00.1374115
OnIdentify 1: Start identify
Tapped 2: Previous token CancelAsync
OnIdentify 2: Start identify
Tapped 3: Previous token CancelAsync
OnIdentify 3: Start identify
Tapped 4: Previous token CancelAsync
OnIdentify 4: Start identify
Tapped 5: Previous token CancelAsync
OnIdentify 5: Start identify
Tapped 6: Previous token CancelAsync
OnIdentify 6: Start identify
Tapped 7: Previous token CancelAsync
OnIdentify 7: Start identify
Tapped 8: Previous token CancelAsync
OnIdentify 8: Start identify
Tapped 9: Previous token CancelAsync
OnIdentify 9: Start identify
Tapped 10: Previous token CancelAsync
OnIdentify 10: Start identify
Tapped 11: Previous token CancelAsync
OnIdentify 11: Start identify
Tapped 12: Previous token CancelAsync
OnIdentify 12: Start identify
Tapped 13: Previous token CancelAsync
OnIdentify 13: Start identify
Tapped 13: Finished after 00:00:12.8716381
Tapped 10: OperationCanceledException after 00:00:13.9292636
Tapped 11: OperationCanceledException after 00:00:13.6609924
Tapped 8: OperationCanceledException after 00:00:15.0679175
Tapped 2: OperationCanceledException after 00:00:18.1325766
Tapped 3: OperationCanceledException after 00:00:17.7673405
Tapped 9: OperationCanceledException after 00:00:14.6338782
Tapped 6: OperationCanceledException after 00:00:16.2799953
Tapped 12: OperationCanceledException after 00:00:13.5238723
Tapped 5: OperationCanceledException after 00:00:16.7167265
Tapped 4: OperationCanceledException after 00:00:17.4615539
Tapped 1: OperationCanceledException after 00:00:18.7209363
Tapped 7: OperationCanceledException after 00:00:15.8465043
If the cancellation worked properly, I would have expected the OperationCancelledException before each Start identify log statement, and the elapsed to not be insane 🙂
I will update the task with a sample webmap when my team has been able to extract the necessary data for public publishing, but hopefully there is enough information to go on here
Is there something that I am missing, or is there a bug going on here?