I had this method (simplified but I think it provides all the necessary context) . . .
public override Envelope GetExtent()
{
var geometry = GetGeometry();
return GeometryEngine.Instance.Expand(geometry.Extent, 1.5, 1.5, true);
}
But when I used that inside Pro, I was treated to an exception with this stack trace . . .
ArcGIS.Core.ConstructedOnWrongThreadException: This object must be created within the lambda passed to QueuedTask.Run, or on a compatible STA thread.
at ArcGIS.Core.CoreObjectsBase.ValidateThread()
at ArcGIS.Core.Internal.Geometry.EnvelopeBuilder..ctor(Envelope envelope, Boolean getHandle)
at ArcGIS.Core.Internal.Geometry.EnvelopeBuilder..ctor(Envelope envelope)
at ArcGIS.Core.Geometry.GeometryEngine.Expand(Envelope envelope, Double dx, Double dy, Boolean asRatio)
at My.Namespace.MyClass.GetExtent() in D:\Whatever\MyClass.cs:line 106
at My.Namespace.MyOtherClass.AnotherMethod() in D:\Whatever\MyOtherClass.cs:line 167
I found that this avoids that exception . . .
public override Envelope GetExtent()
{
var geometry = GetGeometry();
var extent = geometry.Extent;
var geometryEngine = GeometryEngine.Instance;
var expandTask = QueuedTask.Run(() => geometryEngine.Expand(extent, 1.5, 1.5, true);)
return expandTask.Result;
}
But I'm not constructing anything within that QueuedTask. I think this is just a leaky abstraction in the API, but I'm still somewhat new to async and fear I'm missing something, so I'd appreciate anyone pointing out what I'm constructing or why I've gotta run that method (whose doc doesn't call out the need to call it on the MCT) in a QueuedTask.
Solved! Go to Solution.
We have proverb in my native language: "Butter buttered". So, the same thing is with Envelope and Expand method. Envelope has own Expand method:
var sr = SpatialReferenceBuilder.CreateSpatialReference(8826);
MapPoint minPt = MapPointBuilderEx.CreateMapPoint(2456480, 1307526, sr);
MapPoint maxPt = MapPointBuilderEx.CreateMapPoint(2456895, 1307790, sr);
EnvelopeBuilderEx builderEx = new EnvelopeBuilderEx(minPt, maxPt, sr);
Envelope envelope = builderEx.ToGeometry();
var expandedExtent = envelope.Expand(1.5, 1.5, true);
It works as expected without QueuedTask.Run. It could be a bug in GeometryEngine method.
It is a bug in GeometryEngine, and the fix will be in the next release. Fortunately, you have the workaround in the meantime. Thank you for reporting this issue.
Annette
NB FYI we will eventually modify that method to `public override async Task<Envelope> GetExtent()`, but we need other parts of our solution to stabilize first--this is a migration from ArcObjects.
Hi,
Take a look at these:
https://github.com/esri/arcgis-pro-sdk/wiki/ProConcepts-Asynchronous-Programming-in-ArcGIS-Pro
ArcGIS Pro SDK for .NET: Synchronous and Asynchronous Custom Method Design - YouTube
Shortly. If you want something return from asynchronous method you need to use Task<return_data_type>.
That method could contain QueuedTask.Run or orther .NET methos which returns Task.
You can make synchronous method and call QueuedTask.Run outside method. Below two samples with your mehod implemetation:
public Envelope GetExtent()
{
var geometry = GetGeometry();
var extent = geometry.Extent;
var geometryEngine = GeometryEngine.Instance;
return geometryEngine.Expand(extent, 1.5, 1.5, true);
}
var newExtent = await QueuedTask.Run(() =>
{
return GetExtent();
});
or
public Task<Envelope> GetExtentAsync()
{
var geometry = GetGeometry();
var extent = geometry.Extent;
var geometryEngine = GeometryEngine.Instance;
return QueuedTask.Run(() =>
{
return geometryEngine.Expand(extent, 1.5, 1.5, true);
});
}
var newExtent = await GetExtentAsync();
Thank you, GKmieliauskas. But as usual, I wrote too much and obfuscated my own question. My brief "NB FYI" comment indicates that we already plan to refactor our GetExtent method exactly as your second example shows. I'll restate my question here . . .
Why does the GeometryEngine class's Expand(...) method need to be called in QueuedTask.Run? Neither its doc nor the SDK via VisualStudio's intellisense says anything about that need. And why don't I get a CalledOnWrongThreadException for that? I get a ConstructedOnWrongThreadException (we've caused & addressed the former exception many times, but this is my first time seeing the latter) despite my code not constructing anything.
@DanNarsavage_IDWR Yes, you are right. Expand methos doesn't need to be run within QueuedTask.Run.
I think issue is with your returned geometry (from GetGeometry method) extent. Try to check extent for null and for IsEmpty.
@GKmieliauskas In all my experience, GeometryEngine.Expand(...) does need to be called from a QueuedTask, unless the goal is to receive a ConstructedOnWrongThreadException. To demonstrate this more clearly, I've modified my method again, this time using modified portions of the "ConstructEnvelope()" ProSnippets method to make an Envelope from scratch without hiding anything up my sleeve.
public override Envelope GetExtent()
{
var sr = SpatialReferenceBuilder.CreateSpatialReference(8826);
MapPoint minPt = MapPointBuilderEx.CreateMapPoint(2456480, 1307526, sr);
MapPoint maxPt = MapPointBuilderEx.CreateMapPoint(2456895, 1307790, sr);
EnvelopeBuilderEx builderEx = new EnvelopeBuilderEx(minPt, maxPt, sr);
Envelope envelope = builderEx.ToGeometry();
var geometryEngine = GeometryEngine.Instance;
var expandedExtent = geometryEngine.Expand(envelope, 1.5, 1.5, true);
return expandedExtent;
}
Running that still results in a ConstructedOnWrongThreadException...
ArcGIS.Core.ConstructedOnWrongThreadException: This object must be created within the lambda passed to QueuedTask.Run, or on a compatible STA thread.
at ArcGIS.Core.CoreObjectsBase.ValidateThread()
at ArcGIS.Core.Internal.Geometry.EnvelopeBuilder..ctor(Envelope envelope, Boolean getHandle)
at ArcGIS.Core.Internal.Geometry.EnvelopeBuilder..ctor(Envelope envelope)
at ArcGIS.Core.Geometry.GeometryEngine.Expand(Envelope envelope, Double dx, Double dy, Boolean asRatio)
at MyProject.MyClass.GetExtent() in C:\Path\MyClass.cs:line 112
at MyProject.ViewModels.MyViewModel.RunProcessUpdate(Boolean changeExtent) in C:\OtherPath\MyViewModel.cs:line 167
Putting the Expand method in a QueuedTask--and making no other changes--produces the desired results.
public override Envelope GetExtent()
{
var sr = SpatialReferenceBuilder.CreateSpatialReference(8826);
MapPoint minPt = MapPointBuilderEx.CreateMapPoint(2456480, 1307526, sr);
MapPoint maxPt = MapPointBuilderEx.CreateMapPoint(2456895, 1307790, sr);
EnvelopeBuilderEx builderEx = new EnvelopeBuilderEx(minPt, maxPt, sr);
Envelope envelope = builderEx.ToGeometry();
var geometryEngine = GeometryEngine.Instance;
var expandedExtent = QueuedTask.Run(() => geometryEngine.Expand(envelope, 1.5, 1.5, true)).Result;
return expandedExtent;
}
No exceptions are thrown from that version of my GetExtent() method. So I'll drop the deference I've shown to the Pro SDK so far in this thread and say that I think one of these two things is true:
We have proverb in my native language: "Butter buttered". So, the same thing is with Envelope and Expand method. Envelope has own Expand method:
var sr = SpatialReferenceBuilder.CreateSpatialReference(8826);
MapPoint minPt = MapPointBuilderEx.CreateMapPoint(2456480, 1307526, sr);
MapPoint maxPt = MapPointBuilderEx.CreateMapPoint(2456895, 1307790, sr);
EnvelopeBuilderEx builderEx = new EnvelopeBuilderEx(minPt, maxPt, sr);
Envelope envelope = builderEx.ToGeometry();
var expandedExtent = envelope.Expand(1.5, 1.5, true);
It works as expected without QueuedTask.Run. It could be a bug in GeometryEngine method.
Hallo,
There seem to be similar problems in other contexts now (ArcGIS Pro 3.4.2). For example "OnSketchCompleteAsync", all passed sketch geometries contain "ConstructedOnWrongThreadExceptions" .
Thank you, that did the trick. Not sure how I didn't notice that method in the first place.
It is a bug in GeometryEngine, and the fix will be in the next release. Fortunately, you have the workaround in the meantime. Thank you for reporting this issue.
Annette