Select to view content in your preferred language

Multithreading issue with QueuedTask.Run?

952
7
Jump to solution
02-26-2025 04:20 AM
Gurunara
Frequent Contributor

Is QueuedTask.Run thread safe? Is it not intended for multi-threaded environment and we have to implement our own locking mechanism?

Was getting weird error where:

Have method1, and method2.
Each are async methods and within the body of the method, they have QueuedTask.Run(() => { });

The thing is they may both run in a multithreaded context because they may get called concurrently.

When that happens, sequence of call may be:

Method1 gets called, and while method1 is executing within the QueuedTask.Run body, method2 gets called and executes its QueuedTask.Run body.

Method2 exits the QueuedTask.Run body first while method1 is still inside QueuedTask.Run body.

But it seems the exit of QueuedTask.Run body by method2 is causing method1 to exit out of its QueuedTask.Run body (or something)... method1 tries to execute one of the methods within its QueuedTask.Run body, one of the built-in ArcPro SDK functions such as (LayerFactory.Instance.CreateGroupLayer(MapView.Active.Map, 0, containerName)) and it just hangs... 

In debug mode, after stepping over the statement it just seem to exit out of the QueuedTask.Run body or hang or something. It's stuck and have to stop the debug mode execution in VS. When run in non-debug mode, the ArcPro app just gets stuck and have to use task manager to stop the executable.

Current workaround is to use custom SemaphoreSlim implementation and acquire lock before the QueuedTask.Run body is run on both methods, so that those 2 methods never run simultaneously...

But there appears to be multiple method signatures for QueuedTask.Run. Is there perhaps a different call signature for it that would avoid this issue or somthing..?

0 Kudos
2 Solutions

Accepted Solutions
Gurunara
Frequent Contributor

found the real reason for the issue... this doesn't seem to be due to multiple queuedtask's being run:

it was the following series of calls:

if (workflowViewContainer is GroupLayer workflowViewGroupLayer)
MapView.Active.Map.MoveLayer(workflowViewGroupLayer, 0);
if (workflowContainer is GroupLayer workflowGroupLayer)
MapView.Active.Map.MoveLayer(workflowGroupLayer, 0);
if (coreAssetFSContainer is GroupLayer coreAssetFSGroupLayer)
MapView.Active.Map.MoveLayer(coreAssetFSGroupLayer, 0);
if (workflowLocContainer is GroupLayer workflowLocGroupLayer)
MapView.Active.Map.MoveLayer(workflowLocGroupLayer, 0);

inside of queuedtask. it always hangs on one of these MoveLayer calls... i removed them and it starts working... i removed them and set the order initially in LayerFactory.Instance.CreateGroupLayer instead.

there seems to be something wrong (not quite right) with MapView.Active.Map.MoveLayer...

View solution in original post

0 Kudos
Gurunara
Frequent Contributor

Sometimes the first MoveLayer fails.

However, the following seems to work:

QueuedTask.Run(async () =>
{
...
await Task.Run(async () => await QueuedTask.Run(() => MapView.Active.Map.MoveLayer(workflowLocGroupLayer, 0)));

});

Wrapping in Task.Run seems to work. But since MoveLayer has to be run inside QueuedTask.Run, put in another QueuedTask inside.

PS: MapView.Active.Map.MoveStandaloneTable also seems to have this issue...

View solution in original post

0 Kudos
7 Replies
Aashis
by Esri Contributor
Esri Contributor

Have you looked into the Asynchronous Programming in ArcGIS Pro?

0 Kudos
RichardDaniels
Honored Contributor

The documentation says that Queued Tasks run first in, first run methodology. However, that does not guarantee that they will complete in that order. To address this you can add code similar to that shown below to ensure your tasks are complete prior to moving on to your next step.

 

Task t1 = QueuedTask.Run(() => MapView.Active.Map.SetSpatialReference(srWANad83Ft));
Task t2 = QueuedTask.Run(() => MapView.Active.Redraw(false));
Task.WaitAll(t1, t2);

 

or

Task t1 = QueuedTask.Run(() => MapView.Active.Map.SetSpatialReference(srWANad83Ft));

t1.Wait();
Task t2 = QueuedTask.Run(() => MapView.Active.Redraw(false));
t2.Wait();

some would say you could just await the QueuedTask; however, be aware that await is 'fire and forget' and does not actually cause a Wait to happen.

0 Kudos
Gurunara
Frequent Contributor

I do not have this option, whereby one can chain the calls to method1 and method2.
They are independent of each other and are called by different sections of the app independently.
Just that sometimes they may get called simultaneously within a multithreaded env. And that's when the conflict/issue seems to occur. Currently I am using slimsemaphore to lock across both methods so 2 QueuedTask bodies are not called simultaneously. But just thought this should be automatic. I am covering for these 2 methods... but there are other methods too. Do I need to handle locking across all methods..?

Is there something I am not doing right in calling QueuedTask that would guarantee they run independent of each other... or is this conflict due to something else..? Why would QueuedTask exit event in method1 cause any issues with QueuedTask body execution in method2 if they are being executed simultaneously? Is there a parameter in QueuedTask call to ensure independence without using custom semaphore/locking implementation?

0 Kudos
RichardDaniels
Honored Contributor

QueuedTask is used to schedule tasks to run on the MCT (thread). Based on my reading, there is only One MCT thread (please correct me if I'm wrong). They start in the order they are queued BUT we do not know when they will be done. As you say, you start method1, method2 but in some cases method2 is Completed before method1. That is why one can use Task.WaitAll() or Task.Wait() to ensure a result is available for the next Step in our code.

One thing I did Today that appears to be working was refactor my code to reduce the number of nested QueuedTasks and then used Return to explicitly return a result to avoid NullObjectExceptions.

MapPoint mapPoint;
Task<MapPoint> t2a = QueuedTask.Run(() =>
{
MapPoint mapPoint0;
mapPoint0 = MapView.Active.ClientToMap(e.ClientPoint);
//add graphic to the map
AddPointGraphicAsync(mapPoint0);
return mapPoint0;
});
t2a.Wait();
mapPoint = (MapPoint) t2a.Result;
Task t2b = QueuedTask.Run(() =>

 {

//do more work and use the mapPoint for SelectingRowsFromATable, this requires a QueuedTask 

});

 

0 Kudos
Gurunara
Frequent Contributor

found the real reason for the issue... this doesn't seem to be due to multiple queuedtask's being run:

it was the following series of calls:

if (workflowViewContainer is GroupLayer workflowViewGroupLayer)
MapView.Active.Map.MoveLayer(workflowViewGroupLayer, 0);
if (workflowContainer is GroupLayer workflowGroupLayer)
MapView.Active.Map.MoveLayer(workflowGroupLayer, 0);
if (coreAssetFSContainer is GroupLayer coreAssetFSGroupLayer)
MapView.Active.Map.MoveLayer(coreAssetFSGroupLayer, 0);
if (workflowLocContainer is GroupLayer workflowLocGroupLayer)
MapView.Active.Map.MoveLayer(workflowLocGroupLayer, 0);

inside of queuedtask. it always hangs on one of these MoveLayer calls... i removed them and it starts working... i removed them and set the order initially in LayerFactory.Instance.CreateGroupLayer instead.

there seems to be something wrong (not quite right) with MapView.Active.Map.MoveLayer...

0 Kudos
RichardDaniels
Honored Contributor

does MapView.Active.Map.MoveLayer(workflowLocGroupLayer, 0); cause a redraw action on the screen? If so there is a possibility that first MoveLayer is not completed at the time your next call is made.

0 Kudos
Gurunara
Frequent Contributor

Sometimes the first MoveLayer fails.

However, the following seems to work:

QueuedTask.Run(async () =>
{
...
await Task.Run(async () => await QueuedTask.Run(() => MapView.Active.Map.MoveLayer(workflowLocGroupLayer, 0)));

});

Wrapping in Task.Run seems to work. But since MoveLayer has to be run inside QueuedTask.Run, put in another QueuedTask inside.

PS: MapView.Active.Map.MoveStandaloneTable also seems to have this issue...

0 Kudos