20

I am working on a web app with CherryPy that needs to access a few applications via COM.

Right now I create a new instance of the application with each request, which means each request waits 3 seconds for the application to start and 0.01 for the actual job.

I would like to start each COM application once and keep it alive and reuse it for a few seconds on the following requests because most of the time it is used by a burst of 5-10 ajax requests, then nothing for hours.

Is it possible to share a COM abject across all the threads of a CherryPy application?

Here is the summary of a few experiments that show how it is working now on each request and how it does not work across threads.

The following code successfully starts and stops 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()

But the following code starts Excel and closes it after 3 seconds.

>>> 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()

I added the call to CoInitialize() otherwise the xl object would not work (see this post).

And I added the 3 second pause, so I could see on the task manager that the EXCEL.EXE process starts and is alive for 3 seconds.

Why does it die after the thread that started it ends?

I checked the documentation of CoInitialize(), but I couldn't understand if it is possible to get it to work in multithreaded environment.

Community
  • 1
  • 1
stenci
  • 8,290
  • 14
  • 64
  • 104
  • 1
    The trick may be to initialize the thread for multithreaded apartment (aka, free-threaded) use. Try [CoInitializeEx](http://docs.activestate.com/activepython/2.5/pywin32/pythoncom__CoInitializeEx_meth.html) with the `COINIT_APARTMENTTHREADED` option. – tdelaney Nov 05 '14 at 19:50

3 Answers3

37

If you want to use win32com in multiple threads you need to do a little bit of work more as COMObject cannot be passed to a thread directly. You need to use CoMarshalInterThreadInterfaceInStream() and CoGetInterfaceAndReleaseStream() to pass instance between threads:

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()

For more info see: https://mail.python.org/pipermail/python-win32/2008-June/007788.html

Mariusz Jamro
  • 30,615
  • 24
  • 120
  • 162
  • Thanks a lot! I wonder why if there's a loop inside the thread the other threads are waiting. I have added a 'line' argument and was expecting same column cells in each n lines to change simultaneously. – SDIdo May 29 '22 at 11:33
1

The answer from @Mauriusz Jamro ( https://stackoverflow.com/a/27966218/7733418 ) was really helpful. Just to add to it, also ensure that you do:

pythoncom.CoUninitialize ()

in the end so that there's no memory leak. You can call it somewhere after using CoInitialize() and before your process ends.

Yunnosch
  • 26,130
  • 9
  • 42
  • 54
Bipin
  • 21
  • 4
  • You know about the commenting privilege which you do not have, so well that you can even put it into words. You are aware of the rule https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead . In that situation please do not decide to misuse a different mechanism (an answer) for something it is not meant for and which you are not allowed yet to do. – Yunnosch Sep 09 '22 at 06:24
  • Building on an existing answer seems however OK to me.... – Yunnosch Sep 09 '22 at 06:25
-3

Try using multiprocessing. Worked for me, after a long search.

from multiprocessing import Process

p = Process(target=test, args=())
p.start()
p.join()
mpSchrader
  • 902
  • 3
  • 20