来自不同线程的Pythoncom PumpMessages [英] Pythoncom PumpMessages from different thread

查看:88
本文介绍了来自不同线程的Pythoncom PumpMessages的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想做的事情类似于此处.还使用了此处的答案,我的代码可以正常工作,只是没有ItemItem事件可以识别(实际上,我认为是的,但是在另一个线程中,这就是为什么没有输出的原因.)

I want to do something similar to what is asked here, but using threading like here. Using also the answer from here, I got my code working, only that an ItemAdd event is not recognised (actually, I think it is, but in the other thread, which is why there is no output).

"""Handler class that watches for incoming mails"""
import ctypes # for the WM_QUIT to stop PumpMessage()
import logging
import win32com.client
import sys
import threading
import time
import pythoncom

# outlook config
CENTRAL_MAILBOX = "My Mailbox"

# get the outlook instance and inbox folders
outlook = win32com.client.Dispatch("Outlook.Application")
marshalled_otlk = pythoncom.CoMarshalInterThreadInterfaceInStream(
    pythoncom.IID_IDispatch, outlook)


class HandlerClass(object):

    def OnItemAdd(self, item):
        logger.info("New item added in central mailbox")
        if item.Class == 43:
            logger.info("The item is an email!")


class OtlkThread(threading.Thread):

    def __init__(self, marshalled_otlk, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.marshalled_otlk = marshalled_otlk
        self.logger = logging.getLogger("OLThread")

    def run(self):
        self.logger.info("Starting up Outlook watcher\n"
                         "To terminate the program, press 'Ctrl + C'")
        pythoncom.CoInitialize()
        outlook = win32com.client.Dispatch(
            pythoncom.CoGetInterfaceAndReleaseStream(
                self.marshalled_otlk,
                pythoncom.IID_IDispatch
            )
        )
        user = outlook.Session.CreateRecipient(CENTRAL_MAILBOX)
        central_inbox = outlook.Session.GetSharedDefaultFolder(user, 6).Items
        self.logger.info(f"{central_inbox.Count} messages in central inbox")

        win32com.client.DispatchWithEvents(central_inbox, HandlerClass)
        pythoncom.PumpMessages()
        pythoncom.CoUninitialize()  # this is prbly unnecessary as it will never be reached


def main():
    # pythoncom.CoInitialize()
    OtlkThread(marshalled_otlk, daemon=True).start()


if __name__ == "__main__":
    status = main()
    while True:
        try:
            # pythoncom.PumpWaitingMessages()
            time.sleep(1)
        except KeyboardInterrupt:
            logger.info("Terminating program..")
            ctypes.windll.user32.PostQuitMessage(0)
            sys.exit(status)

我尝试了各种方法,例如将 sys.coinit_flags = 0 放在顶部,如建议的

I have tried various things, such as putting sys.coinit_flags=0 at the top, as suggested here), calling PumpWaitingMessages() in the main thread, and getting the Outlook application in the side thread itself, instead of passing the marshalled object. None of these have worked.

当我只是将PumpMessages放入主线程(相同的 HandlerClass ,但没有单独的线程)时,它可以工作,并且在到达时可以识别电子邮件,但是很明显,该线程被阻塞了,甚至无法收到KeyboardInterrupt异常抓到了.

When I just put PumpMessages in the main thread (same HandlerClass but no separate thread) it works and emails are recognised upon arrival, but obviously the thread is blocked and not even the KeyboardInterrupt exception can be caught.

那么,如何使Outlook Watcher在单独的线程中运行,以将消息发送到主线程以在那里输出?

So, how do I get the outlook watcher running in the separate thread to send the messages to the main thread for output there?

推荐答案

再次非常感谢Michael的回答,这导致我这个答案,其中还包含指向优秀示例的链接.从答案和示例中得出的主要结论是,与其将Outlook作为一个编组对象传递,不如将它作为客户端传递给处理程序.另外,要使用 WithEvents 代替 DispatchWithEvents ,并在导入 pythoncom之前将 sys.coinit_flags = 0 设置为.

Again thanks a lot for your answer Michael, it led me to this answer which also contains a link to an excellent example. The main takeaway from the answer and the example is that, instead of passing Outlook as a marshalled object, just pass it as client to the handler. Also, to use WithEvents instead of DispatchWithEvents and to set sys.coinit_flags = 0 before importing pythoncom.

最终结果如下:

"""Handler class that watches for incoming mails"""
import ctypes # for the WM_QUIT to stop PumpMessage()
import logging
import sys
import time
from threading import Thread

sys.coinit_flags = 0  # pythoncom.COINIT_MULTITHREADED == 0
from pythoncom import (CoInitializeEx, CoUninitialize,
                       COINIT_MULTITHREADED, PumpWaitingMessages)
from win32com.client import Dispatch, WithEvents


# outlook config
CENTRAL_MAILBOX = "My Mailbox"


# COM event handler
class IncomingMailHandler:
    def OnItemAdd(self, item):
        logger.info("New item added in central mailbox")
        if item.Class == 43:
            logger.info(f"The item is an email with subject {item.Subject}")


# main thread
def main():
    # get the outlook instance and inbox folders
    outlook = Dispatch("Outlook.Application")
    user = outlook.Session.CreateRecipient(CENTRAL_MAILBOX)
    central_inbox = outlook.Session.GetSharedDefaultFolder(user, 6).Items
    logger.info(f"{central_inbox.Count} messages in central inbox")

    # launch the second thread
    thread = Thread(target=watcher, args=(central_inbox,), daemon=True)
    thread.start()


# other thread worker function
def watcher(client):
    logger = logging.getLogger("watcher")
    CoInitializeEx(COINIT_MULTITHREADED)
    WithEvents(client, IncomingMailHandler)
    # event loop 2
    _loop = 0
    while True:
        PumpWaitingMessages()
        _loop += 1
        if _loop % 20 == 0:
            logger.info("Watcher is running..")
        time.sleep(0.5)
    CoUninitialize()


if __name__ == "__main__":
    logger.info("Starting up Outlook watcher\n"
                "To terminate the program, press 'Ctrl + C'")
    status = main()
    while True:
        try:
            time.sleep(0.5)
        except KeyboardInterrupt:
            logger.info("Terminating program..")
            ctypes.windll.user32.PostQuitMessage(0)
            sys.exit(status)

这篇关于来自不同线程的Pythoncom PumpMessages的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆