'Table panes cannot be accessed from a thread other than the thread they were created on.' Opening a standalone table pane (ArcGIS Pro SDK C#)

243
5
Jump to solution
01-15-2020 01:21 PM
jphilli2Phillips
New Contributor III

Hello, I am experiencing an error when trying to add a standalone table to a map. 

I saw some other threads and borrowed some code from them, but I keep running into this same Error:

The code successfully routes my table (from a local gdb) and adds the routed linework and the underlying standalone table to the map.  However, when I attempt to open the table in the table pane, I keep getting this error. 

Any help would be much appreciated! 

Thanks

John

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ArcGIS.Core.CIM;
using ArcGIS.Core.Data;
using ArcGIS.Core.Geometry;
using ArcGIS.Desktop.Catalog;
using ArcGIS.Desktop.Core;
using ArcGIS.Desktop.Core.Geoprocessing;
using ArcGIS.Desktop.Editing;
using ArcGIS.Desktop.Extensions;
using ArcGIS.Desktop.Framework;
using ArcGIS.Desktop.Framework.Contracts;
using ArcGIS.Desktop.Framework.Dialogs;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Mapping;

namespace BulkLoadTool
{
internal class Tomato : Button
{
protected async override void OnClick()
{
try
{
var lyr = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().First(l => l.Name.Contains("TxDOT_Roadways_Edits"));

//var Datapath =
await QueuedTask.Run(() =>
{
var gdbPath = lyr.GetFeatureClass().GetDatastore().GetPath();
var tblName = lyr.GetTable().GetName();
string fullPath = Path.Combine(gdbPath.LocalPath, tblName);

string in_routes = fullPath; //Datapath.Result
string route_id_field = "GID";
string gPath = Path.Combine(@"C:\TxDOT\BulkLoad\AssetTables", System.IO.File.ReadAllText(@"C:\TxDOT\BulkLoad\LayerReference\configFiles\selGDB.log"));
string inTbl = Convert.ToString(Module1.Current.TblDropDown.SelectedItem);
string inTblPath = Path.Combine(gPath, inTbl);
string in_table = inTblPath;

string in_event_properties = "RDBD_GMTRY_LN_ID LINE ASSET_LN_BEGIN_DFO_MS ASSET_LN_END_DFO_MS";
string out_layer = Module1.Current.TblDropDown.SelectedItem.ToString();

var args = Geoprocessing.MakeValueArray(in_routes, route_id_field, in_table, in_event_properties, out_layer);
string toolPath = @"C:\Program Files\ArcGIS\Pro\Resources\ArcToolBox\Toolboxes\Linear Referencing Tools.tbx\MakeRouteEventLayer";

Geoprocessing.ExecuteToolAsync(toolPath, args);

string sGDB = System.IO.File.ReadAllText(@"C:\TxDOT\BulkLoad\LayerReference\configFiles\selGDB.log");

using (Geodatabase geodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(gPath))))
{
using (Table table = geodatabase.OpenDataset<Table>(inTbl))
{
IStandaloneTableFactory tableFactory = StandaloneTableFactory.Instance;
var insTbl = tableFactory.CreateStandaloneTable(table, MapView.Active.Map, inTbl);
FrameworkApplication.Panes.OpenTablePane(insTbl);
//return insTbl;
}
}


});

}
catch (Exception e)
{
Trace.WriteLine(e.Message);
}
}

}

}

Reply
0 Kudos
1 Solution

Accepted Solutions
RichRuh
Esri Regular Contributor

Than is on the right track.  The Geodatabase constructor and the call to Geodatabase.OpenDataset<Table> at the end of the snippet should also be inside a QueuedTask.Run block.  The call to OpenTablePane should not.  

Try the following:

if (!ProcessingReuslt.IsFailed)
{
Trace.WriteLine("Success");

var insTbl;

await QueuedTask.Run(()=>
{
using (Geodatabase geodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(gPath))))
{
using (Table table = geodatabase.OpenDataset<Table>(inTbl))
{
IStandAloneTableFactory tableFactory = StandaloneTableFactory.Instance;
insTbl = tableFactory.CreateStandaloneTable(table, MapView.Active.Map, inTbl);
}
}
}

FrameworkApplication.Panes.OpenTablePane(insTbl);
}

This will open the table on the MCT (geodatabase thread) and open the table pane on a UI thread.

I hope this helps,

--Rich

View solution in original post

5 Replies
ThanHtetAung_EsriAu
Esri Regular Contributor

Hi @John Phillips,

How about trying to wrap the queuetask for the require code only.

And add await for geoprocessing task, this is async thread and if there is no await , next statement shall run and end up another statement is running before finishing gp task.

Below is the sample update, try and let me know

     try
{
var lyr = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().First(l => l.Name.Contains("TxDOT_Roadways_Edits"));
//var Datapath =
Uri gdbPath = null;
string tblName = "";
string fullPath = "";
await QueuedTask.Run(() =>
{
gdbPath = lyr.GetFeatureClass().GetDatastore().GetPath();
tblName = lyr.GetTable().GetName();
fullPath = Path.Combine(gdbPath.LocalPath, tblName);

});
string in_routes = fullPath; //Datapath.Result
string route_id_field = "GID";
string gPath = Path.Combine(@"C:\TxDOT\BulkLoad\AssetTables", System.IO.File.ReadAllText(@"C:\TxDOT\BulkLoad\LayerReference\configFiles\selGDB.log"));
string inTbl = Convert.ToString(Module1.Current.TblDropDown.SelectedItem);
string inTblPath = Path.Combine(gPath, inTbl);
string in_table = inTblPath;
string in_event_properties = "RDBD_GMTRY_LN_ID LINE ASSET_LN_BEGIN_DFO_MS ASSET_LN_END_DFO_MS";
string out_layer = Module1.Current.TblDropDown.SelectedItem.ToString();
var args = Geoprocessing.MakeValueArray(in_routes, route_id_field, in_table, in_event_properties, out_layer);
string toolPath = @"C:\Program Files\ArcGIS\Pro\Resources\ArcToolBox\Toolboxes\Linear Referencing Tools.tbx\MakeRouteEventLayer";
var gpResult = Geoprocessing.ExecuteToolAsync(toolPath, args);
gpResult.Wait();//Wait for geoprocessing task to finish
IGPResult ProcessingResult = gpResult.Result;
if (!ProcessingResult.IsFailed)
{
string sGDB = System.IO.File.ReadAllText(@"C:\TxDOT\BulkLoad\LayerReference\configFiles\selGDB.log");
using (Geodatabase geodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(gPath))))
{
using (Table table = geodatabase.OpenDataset<Table>(inTbl))
{
IStandaloneTableFactory tableFactory = StandaloneTableFactory.Instance;
var insTbl = tableFactory.CreateStandaloneTable(table, MapView.Active.Map, inTbl);
FrameworkApplication.Panes.OpenTablePane(insTbl);
//return insTbl;
}
}
}



}
catch (Exception e)
{
Trace.WriteLine(e.Message);
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
jphilli2Phillips
New Contributor III

Hello Than,

Thank you for your response.  I tried the code that you provided and unfortunately I am still experiencing the same error.

The IGPResult is successful and the code is reaching the OpenTablePane call. Thanks for your help!

Reply
0 Kudos
RichRuh
Esri Regular Contributor

Than is on the right track.  The Geodatabase constructor and the call to Geodatabase.OpenDataset<Table> at the end of the snippet should also be inside a QueuedTask.Run block.  The call to OpenTablePane should not.  

Try the following:

if (!ProcessingReuslt.IsFailed)
{
Trace.WriteLine("Success");

var insTbl;

await QueuedTask.Run(()=>
{
using (Geodatabase geodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(gPath))))
{
using (Table table = geodatabase.OpenDataset<Table>(inTbl))
{
IStandAloneTableFactory tableFactory = StandaloneTableFactory.Instance;
insTbl = tableFactory.CreateStandaloneTable(table, MapView.Active.Map, inTbl);
}
}
}

FrameworkApplication.Panes.OpenTablePane(insTbl);
}

This will open the table on the MCT (geodatabase thread) and open the table pane on a UI thread.

I hope this helps,

--Rich

View solution in original post

jphilli2Phillips
New Contributor III

Rich, that worked!  Thank you so much for yours and Than Aung‌ 's help! 

Also, thanks for explaining the difference between the MCT and UI threading here.. I remember learning about that but hadn't explicitly put it to use.  Looking forward to the Dev Summit this year to learn more

Reply
0 Kudos
RichRuh
Esri Regular Contributor

Glad it worked.  See you in Palm Springs!

Reply
0 Kudos