Pythoncom - 将相同的 COM 对象传递给多个线程

Pythoncom - Passing same COM object to multiple threads

你好 :) 在 COM 对象方面,我是一个完全的初学者,感谢任何帮助!

我正在开发一个 Python 程序,该程序应该以 client/server 方式读取传入的 MS-Word 文档,即客户端发送请求(一个或多个 MS-Word 文档)并且服务器使用 pythoncom 和 win32com 从这些请求中读取特定内容。

因为我想尽量减少客户端的等待时间(客户端需要来自服务器的状态消息,我不想为每个请求打开一个 MS-Word 实例。因此,我打算有一个 运行 服务器可以从中选择的 MS-Word 实例。反过来,这意味着我必须在不同的线程中重用池中的这些实例,这就是现在造成麻烦的原因。在我阅读 Using win32com with multithreading,我的服务器虚拟代码如下所示:

import pythoncom, win32com.client, threading, psutil, os, queue, time, datetime

appPool = {'WINWORD.EXE': queue.Queue()}

def initAppPool():
    global appPool
    wordApp = win32com.client.DispatchEx('Word.Application')
    appPool["WINWORD.EXE"].put(wordApp) # For testing purpose I only use one MS-Word instance currently

def run_in_thread(appid, path):
    #open doc, read do some stuff, close it and reattach MS-Word instance to pool
    pythoncom.CoInitialize()
    wordApp = win32com.client.Dispatch(pythoncom.CoGetInterfaceAndReleaseStream(appid, pythoncom.IID_IDispatch))
    doc = wordApp.Documents.Open(path)
    time.sleep(3) # read out some content ...
    doc.Close()
    appPool["WINWORD.EXE"].put(wordApp)

if __name__ == '__main__':
    initAppPool()

    pathOfFile2BeRead1 = r'C:\Temp\file4.docx'
    pathOfFile2BeRead2 = r'C:\Temp\file5.doc'

    #treat first request
    wordApp = appPool["WINWORD.EXE"].get(True, 10) 
    pythoncom.CoInitialize()
    wordApp_id = pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, wordApp) 
    readDocjob1 = threading.Thread(target=run_in_thread,args=(wordApp_id,pathOfFile2BeRead1), daemon=True)
    readDocjob1.start() 

    #wait here until readDocjob1 is done 
    wait = True
    while wait:
        try:
            wordApp = appPool["WINWORD.EXE"].get(True, 1)
            wait = False
        except queue.Empty:
            print(f"[{datetime.datetime.now()}] error: appPool empty")
        except BaseException as err:
            print(f"[{datetime.datetime.now()}] error: {err}")
    

到目前为止一切都按预期进行,但是当我开始第二个请求时与第一个请求类似:

(x) wordApp_id = pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, wordApp)
    readDocjob2 = threading.Thread(target=run_in_thread,args=(wordApp_id,pathOfFile2BeRead2), daemon=True)
    readDocjob2.start()

我收到以下错误消息:对于标记为 (x) 的行,“应用程序调用了一个为不同线程编组的接口”。

我想这就是为什么我必须使用 pythoncom.CoGetInterfaceAndReleaseStream 在具有相同 COM 对象的线程之间跳转?还有,为什么第一次可以,第二次不行?

我在 Whosebug 上搜索了使用 CoMarshalInterface 而不是 CoMarshalInterThreadInterfaceInStream 的不同解决方案,但它们都给了我同样的错误。我现在真的很困惑。

编辑: 修复评论中提到的错误后,我 运行 变成了一个神秘的行为。 当执行第二个作业时:

    wordApp_id = pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, wordApp)
    readDocjob2 = threading.Thread(target=run_in_thread,args=(wordApp_id,pathOfFile2BeRead2), daemon=True)
    readDocjob2.start()

函数 run_in_thread 立即终止,没有执行任何行,分别似乎 pythoncom.CoInitialize() 没有正常工作。 尽管脚本完成时没有任何错误消息。

def run_in_thread(instance,appid, path):
    #open doc, read do some stuff, close it and reattach MS-Word instance to pool
    pythoncom.CoInitialize()
    wordApp = win32com.client.Dispatch(pythoncom.CoGetInterfaceAndReleaseStream(appid, pythoncom.IID_IDispatch))
    doc = wordApp.Documents.Open(path)
    time.sleep(3) # read out some content ...
    doc.Close()
    instance.flag = True

如果您将从 CoGetInterfaceAndReleaseStream. But this reference was created specially for this new thread and then you call CoMarshalInterThreadInterfaceInStream 获得的 COM 引用放回到“activePool”中,就会发生这种情况。

这是错误的。

您必须始终使用从创建它的线程获得的原始 COM 引用,以便能够重复调用CoMarshalInterThreadInterfaceInStream

因此,要解决此问题,您必须更改应用程序池的工作方式,使用某种“正在使用”标志,但不要触及原始 COM 引用。