关于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。为了确保线程安全性,应该将操作封闭在一个线程中,避免跨线程实例同一个实例。

posted @ 2024-01-15 14:52  lythen  阅读(92)  评论(0编辑  收藏  举报