I have a scenario where I need to cycle through some tiles of the map and export them on tif and pdf files. The code I've written to achieve the task is ok and it works fine. Since the operation can be very long, depending on the number of source items, I'd like to show a cancelable progress window to the user so that he can stop the job if needed.
The window appears, but the Export operation prevents message, status and CancellationToken from updating. The result is a window which can only show the initial status and message, and cannot be canceled (you can press the button, and ironically it updates the window with the cancel message, but the property progressorSource.Progressor.CancellationToken.IsCancellationRequested is always false).
To show this, I managed to strip down some code about PDF export to fit it inside a single, simplified function:
private async Task ExportTilesAsync(Layout layout, string whereClause)
{
string outputFolder = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), "SOME_OUTPUT_FOLDER");
var ctrMapFrame = layout.FindElement("MAPPA") as MapFrame;
var ctrMap = ctrMapFrame.Map;
var progressorSource = new CancelableProgressorSource("Processing...", "Cancelling...");
await QueuedTask.Run(() =>
{
//Get Layer
var layer = ctrMap.FindLayers("qu05_pl").OfType<FeatureLayer>().FirstOrDefault();
//Query layer
FeatureClass pFeatClass = layer.GetFeatureClass();
var nCount = pFeatClass.GetCount();
progressorSource.Progressor.Max = (uint)nCount;
QueryFilter queryFilter = new QueryFilter() { WhereClause = whereClause };
using (RowCursor rowCursor = layer.Search(queryFilter))
{
while (rowCursor.MoveNext())
{
if (progressorSource.Progressor.CancellationToken.IsCancellationRequested)
return;
var currentFeature = rowCursor.Current as Feature;
var idTile = currentFeature.GetFieldValue("ELEMENTO").ToString();
progressorSource.Progressor.Value += 1;
progressorSource.Progressor.Status = $"Processing Tile {idTile}...";
ClipAndRefreshMap(currentFeature, ctrMapFrame);
PDFFormat PDF = new PDFFormat();
PDF.Resolution = 400;
PDF.OutputFileName = Path.Combine(outputFolder, $"{idTile}");
PDF.ImageCompression = ImageCompression.LZW;
PDF.DoEmbedFonts = true;
PDF.DoCompressVectorGraphics = true;
PDF.DoClipToGraphicExtent = false;
//Export active mapView view
if (PDF.ValidateOutputFilePath())
{
layout.Export(PDF);
}
}
}
}, progressorSource.Progressor);
}
ClipAndRefreshMap is the only external function called here, it sets extent and view of the MapFrame in the right way for the current tile (feature) to export. To keep it as short as possible (and since it does not appear to be the problem) I'm omitting the code inside this function, but I can provide it if needed.
If I just remove the call to layout.Export from this code (you can add a Task.Delay() call to make it slower, if needed), everything works: I get message and status update at every cycle and, if I cancel the job, the code intercepts it properly and exits.
I've tried to mess around with threads and priorities but never really found anything useful: all I obtained is to enqueue all the actual exports after the cycle is done, which is obviously not useful. As far as I understand the way I set up the CancelableProgressorSource is ok, I guess it would be useful to have an ExportAsync() method, but it seems like only the synchronous method is available. Is there something about Export operation that prevents it to work properly? How am I supposed to implement my scenario?
Any suggestion is appreciated, thanks.