web-dev-qa-db-ja.com

マルチスレッドでwin32comを使用する

COM経由でいくつかのアプリケーションにアクセスする必要があるCherryPyを使用してWebアプリに取り組んでいます。

現在、リクエストごとにアプリケーションの新しいインスタンスを作成しています。つまり、各リクエストは、アプリケーションが開始するまで3秒、実際のジョブが0.01秒待機します。

各COMアプリケーションを一度起動し、存続させて、次のリクエストで数秒間再利用したいと思います。ほとんどの場合、5〜10個のajaxリクエストのバーストで使用され、その後何時間も使用されないためです。

CherryPyアプリケーションのすべてのスレッド間でCOMオブジェクトを共有することは可能ですか?

これは、各リクエストで現在どのように機能しているか、スレッド間でどのように機能しないかを示すいくつかの実験の要約です。

次のコードは、Excelを正常に起動および停止します。

_>>> import pythoncom, win32com.client
>>> def start():
    global xl
    xl = win32com.client.Dispatch('Excel.Application')

>>> def stop():
    global xl
    xl.quit()
    xl = None

>>> start()
>>> stop()
_

ただし、次のコードはExcelを起動し、3秒後に閉じます。

_>>> import pythoncom, win32com.client, threading, time
>>> def start():
    global xl
    pythoncom.CoInitialize()
    xl = win32com.client.Dispatch('Excel.Application')
    time.sleep(3)

>>> threading.Thread(target=start).start()
_

CoInitialize()への呼び出しを追加しました。そうしないと、xlオブジェクトが機能しません( この投稿 を参照)。

そして、3秒間の一時停止を追加したので、タスクマネージャーで、Excel.EXEプロセスが開始され、3秒間有効であることがわかりました。

それを開始したスレッドが終了した後、なぜそれが死ぬのですか?

CoInitialize() のドキュメントを確認しましたが、マルチスレッド環境で動作させることができるかどうかわかりませんでした。

14
stenci

複数のスレッドでwin32comを使用する場合は、COMObjectをスレッドに直接渡すことができないため、もう少し作業を行う必要があります。スレッド間でインスタンスを渡すには、CoMarshalInterThreadInterfaceInStream()CoGetInterfaceAndReleaseStream()を使用する必要があります。

import pythoncom, win32com.client, threading, time

def start():
    # Initialize
    pythoncom.CoInitialize()

    # Get instance
    xl = win32com.client.Dispatch('Excel.Application')

    # Create id
    xl_id = pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, xl)

    # Pass the id to the new thread
    thread = threading.Thread(target=run_in_thread, kwargs={'xl_id': xl_id})
    thread.start()

    # Wait for child to finish
    thread.join()

def run_in_thread(xl_id):
    # Initialize
    pythoncom.CoInitialize()

    # Get instance from the id
    xl = win32com.client.Dispatch(
            pythoncom.CoGetInterfaceAndReleaseStream(xl_id, pythoncom.IID_IDispatch)
    )
    time.sleep(5)


if __name__ == '__main__':
    start()

詳細については、以下を参照してください: https://mail.python.org/pipermail/python-win32/2008-June/007788.html

22
Mariusz Jamro