Pythoncom PumpMessages из разных потоков - PullRequest
0 голосов
/ 20 февраля 2019

Я хочу сделать что-то похожее на то, что спрашивается здесь , но с использованием threading, как здесь .Используя также ответ из здесь , я получил свой код работающим, только то, что событие ItemAdd не распознается (на самом деле, я думаю, что это так, но в другом потоке, поэтому нет вывода).

"""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 вверху, как предлагалось здесь ), вызывал PumpWaitingMessages() в главном потоке и получал приложение Outlookв самой боковой нити, вместо прохождения маршаллированного объекта.Ничего из этого не сработало.

Когда я просто помещаю PumpMessages в основной поток (тот же HandlerClass, но без отдельного потока), он работает, и электронные письма распознаются по прибытии, но, очевидно, поток блокируется, и даже неИсключение KeyboardInterrupt может быть перехвачено.

Итак, как мне заставить наблюдателя outlook работать в отдельном потоке для отправки сообщений в основной поток для вывода там?

Ответы [ 2 ]

0 голосов
/ 06 марта 2019

Еще раз большое спасибо за ваш ответ, Майкл, это привело меня к этому ответу, который также содержит ссылку на отличный пример .Основной вывод из ответа и примера состоит в том, что вместо передачи Outlook в качестве маршаллированного объекта, просто передайте его как клиент обработчику.Кроме того, для использования WithEvents вместо DispatchWithEvents и установки sys.coinit_flags = 0 перед импортом 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)
0 голосов
/ 03 марта 2019

Красиво отформатированный вопрос со ссылками.Благодарю.

К ответу.В этих случаях я использую `` threading.Thread (target = .... `. Однако вы также можете использовать наследование:

import logging
import threading
import time


def task(name):
    log = logging.getLogger('task-' + name)
    log.info("Starting up Outlook watcher\n"
                     "To terminate the program, press 'Ctrl + C'")
    while True:
        log.info("Doing work that takes time")
        time.sleep(1)


class OtlkThread(threading.Thread):

    def __init__(self, *args, **kwargs):
        self.log = logging.getLogger('task-class')
        super().__init__(*args, **kwargs)

    def run(self):
        self.log.info("Starting up Outlook watcher\n"
                 "To terminate the program, press 'Ctrl + C'")
        while True:
            self.log.info("Doing work that takes time")
            time.sleep(1)

def main():
    t1 = threading.Thread(target=task, args=('daemon',), daemon=True)
    t1.start()
    t2 = OtlkThread()
    t2.start()


if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG,)

    main()
    while True:
        time.sleep(1)
...