Mapfactory fails (without error) when called from within function- issues with QueuedTask?

501
3
08-12-2021 06:34 PM
AndrewDunningham
New Contributor

Hi I have code that creates series of maps in a project (then add lots of data layers, calculates a common legend across 5 timer periods and creates layouts (5 frames) for each climate parameter)  (added as a button with ArcPro)

 

When in the main class it works

When put into a function it doesn't work - not error just exits the function

           - tried with 2 async methods (private async void... and private async Task ...

(added as a button with ArcPro)

CODE

This works:

=======================================

protected async override void OnClick()
{

<declarations>

foreach (string map in Mapname_Series)
{

//Create the maps
Map newMap = await QueuedTask.Run<Map>(() =>
{
     Map newMap2 = MapFactory.Instance.CreateMap(map, MapType.Map, MapViewingMode.Map, Basemap.None);

     QueuedTask.Run(() =>
     {
    ArcGIS.Core.Geometry.SpatialReference sr2193 = SpatialReferenceBuilder.CreateSpatialReference(2193);
newMap2.SetSpatialReference(sr2193);
});

=======================

This doesn't

 

private async void CreateMaps(string[] variables, string[] rcps, string[] periods, Boolean UseSDE, string region, string region_sd)
{
<declarations>

foreach (string map in Mapname_Series)
{

//Create the maps
Map newMap = await QueuedTask.Run<Map>(() =>
{
Map newMap2 = MapFactory.Instance.CreateMap(map, MapType.Map, MapViewingMode.Map, Basemap.None);
QueuedTask.Run(() =>
{
ArcGIS.Core.Geometry.SpatialReference sr2193 = SpatialReferenceBuilder.CreateSpatialReference(2193);
newMap2.SetSpatialReference(sr2193);
});

... < goes on to add data to each map>

}

 

 

0 Kudos
3 Replies
KenBuja
MVP Honored Contributor

A couple of comments on your code that may not have anything to do with your error. You should move the code for creating the spatial reference outside the loop, since it only needs to be created once. Also see the documentation on Using QueuedTask, where it talks about the incremental cost of creating each new QueuedTask.Run. It suggests consolidating them

 //Avoid this...
 foreach(var some_item in ...) {
   await QueuedTask.Run(() => { //QTR inside the loop
     //do work
   });
 }

 //Prefer this...
 await QueuedTask.Run(() => { //QTR outside the loop
    foreach(var some_item in ...) {
     //do work
    }
 });

 

0 Kudos
CharlesMacleod
Esri Regular Contributor

The problem is most likely your function signature: 

private async void CreateMaps(....)

It is marked "async" but returns void. Instead, change it to:

private async Task CreateMaps(...)

and u shld get the behavior u are expecting. Notice it now returns _Task_.

I would recommend the following articles and resources to help u on async/await and asynchronous programming in general and they will also help explain the difference between the two method signatures above (and why return Task):

ProConcepts-Framework#working-with-multithreading-in-arcgis-pro 

ProConcepts-Asynchronous-Programming-in-ArcGIS-Pro 

We also have some videos online: Pro SDK Video Playlists - You might find our intro course helpful:  ArcGIS Pro SDK for .NET: Beginning Pro Customization with focus on DAML and Customization Patterns - minute mark 46.23. This particular one was from 2019. Also ArcGIS Pro SDK for .NET: Synchronous and Asynchronous Custom Method Design  from 2021.

Last, I would also recommend restructuring your method internals to be something like as follows. You will also find this explained in the above resources:

private Task<List<Map>> CreateMapsAsync(string[] variables, string[] rcps, string[] periods, Boolean UseSDE, string region, string region_sd)
		{
			return QueuedTask.Run(() => CreateMaps(variables, rcps, periods, UseSDE, region, region_sd));
		}

		private List<Map> CreateMaps(string[] variables, string[] rcps, string[] periods, Boolean UseSDE, string region, string region_sd)
		{
			//< declarations >
                        //...
			List<Map> maps = new List<Map>();

			var sr2193 = SpatialReferenceBuilder.CreateSpatialReference(2193);
			foreach (string map in Mapname_Series)
			{
				//Create the maps
				var newMap = MapFactory.Instance.CreateMap(map, MapType.Map, MapViewingMode.Map, Basemap.None);
				newMap.SetSpatialReference(sr2193);
				//... < goes on to add data to each map >
				maps.Add(newMap);
			}
			return maps;
		}

//USAGE:
//var maps = await CreateMapsAsync(...)
//or use the synchronous version if u are already within another QueuedTask
//var maps = CreateMaps(....)

 

 

 

 

 

Wolf
by Esri Regular Contributor
Esri Regular Contributor

To elaborate a bit on Ken's suggestion above and what Charlie put into practice, you want to avoid a pattern where QueuedTask.Run () is called from within the context of a QueuedTask.Run action.  This can lead to dead locks.  

When you said: "When put into a function it doesn't work - not error just exits the function".  This is expected behavior for any asynchronous function if you call the method without specifying an 'await'.  In your question above you didn't show how you called the method which didn't work.  Put a breakpoint into your 'async void CreateMaps' method and see if you hit that breakpoint (you should hit it actually).  

Also you should always add some error handling using a try {} catch {}.

0 Kudos