跨线程封送COM对象后与服务器断开连接的解决方法咨询
COM跨线程访问报错问题
实现代码
我使用并发方案获取COM接口对象的解析器/包装器集合,实现代码如下:
def _get_interface_containers_concurrent( self, com_interface_containers: List["win32com"], interface_container_type: Type[InterfaceContainer] ) -> List[InterfaceContainer]: """Get interface containers concurrently. .. note:: see the class docstring for the current caveats of using this. :param com_interface_containers: a list of COM objects that correspond to an interface container :param interface_container_type: a type to wrap with the parser Element object (that wraps the COM one) :return: a list of wrapped interface containers """ def add_ic(id_, repoid, lst: list, ic_type: Type[InterfaceContainer]) -> None: pythoncom.CoInitialize() ic_obj = win32com.client.Dispatch( pythoncom.CoGetInterfaceAndReleaseStream(id_, pythoncom.IID_IDispatch)) repo_obj = win32com.client.Dispatch( pythoncom.CoGetInterfaceAndReleaseStream(repoid, pythoncom.IID_IDispatch)) ic_el = Element(ic_obj) print(f"{ic_el.name!r} has been parsed.") ic = ic_type(ic_el, repo_obj) lst.append(ic) print(f"{ic.name!r} {ic_type.__name__} created.") result, workers = [], [] for com_ic in com_interface_containers: ic_id = pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, com_ic) repo_id = pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, self._repo) t = Thread(target=add_ic, args=(ic_id, repo_id, result, interface_container_type)) workers.append(t) t.start() for w in workers: w.join() print("Parsing completed.") return result
问题表现
该实现可正常运行,仅访问包装对象上已解析存储的数据时无异常,但在线程合并回主线程后尝试访问被包装的COM对象时,会抛出如下错误:com_error: (-2147220995, 'Object is not connected to server', None, None)
问题原因
错误的核心根源是COM的套间线程模型限制:子线程调用CoInitialize默认初始化的是单线程套间(STA),子线程内解封得到的COM对象归该STA套间所有,子线程执行完毕退出后,套间会被销毁,持有子线程COM对象引用的主线程自然会失去和COM服务器的连接。
规避方案
- 方案1:二次封送COM对象回主线程
子线程构造完包装对象后,不要直接返回持有子线程COM引用的对象,而是把包装对象内的COM实例再次封送为流ID存到包装对象内,主线程join所有子线程后,再遍历所有返回的包装对象,把流ID解封为主线程套间可用的COM实例,替换原有引用即可。
另外需在add_ic函数末尾补充pythoncom.CoUninitialize(),主动释放子线程的COM环境,避免资源泄漏。 - 方案2:全局使用多线程套间(MTA)
如果你的COM组件支持多线程套间,可将主线程和所有子线程的COM初始化逻辑替换为pythoncom.CoInitializeEx(pythoncom.COINIT_MULTITHREADED),所有线程的COM对象会归属同一个MTA套间,可跨线程直接访问,不需要额外封送操作。
注意:该方案依赖COM服务器的实现,单线程套间组件强行使用MTA会出现未知异常。 - 方案3:调整业务逻辑仅返回纯数据
如果主线程不需要后续操作COM对象,可直接在子线程内完成所有COM操作,仅把最终需要的纯数据结果返回给主线程,完全避免跨线程持有COM引用,这是兼容性最高、最稳妥的解决方案。
内容的提问来源于stack exchange,提问作者z33k
相关产品推荐
相关产品推荐

