Python线程未使用atexit退出

问题描述

这是我的脚本。当我在外壳中运行它时,它只是无限期地挂起,而我希望它干净地终止。

import logging
from logging import StreamHandler
import pymsteams
import queue
import threading
import atexit


class TeamsHandler(StreamHandler):
    def __init__(self, channel_url):
        super().__init__()
        self.channel_url = channel_url
        self.queue = queue.Queue()
        self.thread = threading.Thread(target=self._worker)
        self.thread.start()
        atexit.register(self.queue.put, None)

    def _worker(self):
        while True:
            record = self.queue.get()
            if record is None:
                break
            msg = self.format(record)
            print(msg)

    def emit(self, record):
        # enqueue the record to log and return control to the caller
        self.queue.put(record)


if __name__ == "__main__":
    my_logger = logging.getLogger('TestLogging')
    my_logger.setLevel(logging.DEBUG)
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.DEBUG)
    my_logger.addHandler(console_handler)

    CHANNEL_ID = "not_used_anyway"
    teamshandler = TeamsHandler(CHANNEL_ID)
    teamshandler.setFormatter(logging.Formatter('%(levelname)s %(message)s'))
    teamshandler.setLevel(logging.DEBUG)
    my_logger.addHandler(teamshandler)
    for i in range(1, 2):
        my_logger.error(f"this is an error [{i}]")
        my_logger.info(f"this is an info [{i}]")

应由atexit(第28行)发送的None记录从未到达,因此线程始终保持打开状态。

如何通过仅修改TeamsHandler确保程序干净退出?


解决方案

正如avysk指出的,问题可能是atexit处理程序启动太晚,在等待非后台进程线程已经(应该)完成之后,这会导致死锁。

如果我是你,我只会在if __name__ == '__main__'块的末尾添加一个类似TeamsHandler.finish()的调用,并按如下方式修改TeamsHandler(未测试):

_queues = []

class TeamsHandler(StreamHandler):
    def __init__(self, channel_url):
        super().__init__()
        self.channel_url = channel_url
        self.queue = queue.Queue()
        self.thread = threading.Thread(target=self._worker)
        self.thread.start()
        _queues.append(self.queue)

    def _worker(self):
        while True:
            record = self.queue.get()
            if record is None:
                break
            msg = self.format(record)
            print(msg)

    def emit(self, record):
        # enqueue the record to log and return control to the caller
        self.queue.put(record)

    @staticmethod
    def finish(self):
        for q in _queues:
            q.put(None)
        del _queues[:]

相关文章