关于ArcEngine在多线程模式下的注意点,记“无法将类型为"Systerm._ComObject"的对象强制转换为类型FileGDBWorkspaceFactory()”解决经过
仅以我的环境来描述的我问题和解决方案,超出该范围的暂时没有考虑。
一、环境
ArcEngine 10.2
语言:C#
.net版本:4.6.1
二、需求
创建GDB数据库,并从json文件把数据写入GDB中,包含了图形数据,为了兼顾效率,我使用了多线程来生成GDB,但也做了控制,一个线程只会对一个GDB进行操作。
三、问题:
无法将类型为"Systerm._ComObject"的对象强制转换为类型FileGDBWorkspaceFactory();
四、问题分析
这个问题困扰我很久,在网上找过好几个解决方案,但都不适用,因为报的是COM组件的问题,当时一直认为是AE不支持多线程操作导致,于是做了一个处理,就是在创建或读取GDB的时候,加了个循环,出错了就隔10秒再读,一共5次重试。
这个方案一定程度上缓解了问题,但并没有解决,因为连续5次都以上错的概率,还是挺高的。
当时我其实也是把问题推给了AE,就跟实施说,这个是AE的问题,解决不了,除非换单线程,但是换单线程导出的速度又不行,所以在这里纠结了很长时间。
直到上周五吧,我突然留意到一个问题,报错的位置,并不是在打开或者创建GDB那一行,而是在实例化工厂那一行,也就是下面这行:
IWorkspaceFactory2 wsFctry = new FileGDBWorkspaceFactoryClass();
我突然觉得不对劲,这里还没到具体的操作GDB呢,别问我为什么现在才发现报错在这行,因为我一直都觉得是AE的问题,我一直都认为AE在多线程模式下是有问题的,出错是理所当然的,只能绕过去,所以我压根没有细细去想,去分析。唉。。多么痛的领悟。
后来查找到一个解决方案,就是使用反射的方式创建工厂,如下:
Type t = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory"); System.Object obj = Activator.CreateInstance(t); var gdbWorkspaceFactory = obj as IWorkspaceFactory;
OK,到这里,创建工厂的问题确实解决了,后面没有再报这个错,可是!!问题又来了
报错:COM组件异常来自 HRESULT:0x80010105 (RPC_E_SERVERFAULT)的错误,所以,我考虑,仍然是多线程的问题,另外,目前这种创建方式,每操作一次GDB,就创建一个工厂,用来操作GDB,这可能会导致同时创建了大量的工厂,没这个必要。于是,根据目前的情况,改造点有两个:
1、避免创建过多厂,只创建一个即可
2、操作帮助类的时候,将帮助类封闭在一个线程,不出现任何跨线程变量共享和情况。
经改造,关键代码如下:
private IWorkspaceFactory gdbWorkspaceFactory = null; /// <summary> /// 读取GDB工作空间 /// </summary> /// <param name="FileGDBPath">FileGDBPath</param> /// <returns>IWorkspace</returns> public IWorkspace OpenFileGDBWorkspace(string FileGDBPath) { lock (locker) { MakeFactory(); FileGDBPath = System.IO.Path.GetFullPath(FileGDBPath); return this.gdbWorkspaceFactory.OpenFromFile(FileGDBPath, 0); } } private void MakeFactory() { if (this.gdbWorkspaceFactory == null) { Type t = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory"); System.Object obj = Activator.CreateInstance(t); this.gdbWorkspaceFactory = obj as IWorkspaceFactory; } }
在每个线程中,独立实例化一个类来操作GDB:
Task.run(()=>{ Export(); }); void Export(){ GdbHelper gdbHelper = new GdbHelper(); gdbHelper.OpenFileGDBWorkspace(path); }
以上只是演示代码,不是我的实际代码。
经过一晚上的压测,将COM操作封闭在一个线程中,保证一个线程中只有一 个工厂实例后,没有再报错,至此,暂时证明了这个问题可以用这个方法解决。
五、总结
这个问题之所以这么久才解决,主要原因还是因为自己对AE不太熟悉,遇到问题,没有认真去仔细分析,而是把问题推掉。如果一开始就静下心来去分析问题,可能早就解决了。
这个问题其实确实是多线程导致,在多线程环境中使用 COM 组件时需要注意线程安全性和 COM 组件安全性。COM 组件在设计时可能是单线程单元(STA - Single-Threaded Apartment)的,这意味着它们在单个线程上运行,而不是设计为在多线程环境中并发使用。
在 ArcObjects 中,许多组件是 STA 的,包括 FileGDBWorkspaceFactoryClass。为了确保线程安全性,应该将操作封闭在一个线程中,避免跨线程实例同一个实例。