使用 irc.bot.SingleServerIRCBot 持续存在的线程(与 twitch 一起使用)

Posted

技术标签:

【中文标题】使用 irc.bot.SingleServerIRCBot 持续存在的线程(与 twitch 一起使用)【英文标题】:Threads persisting with irc.bot.SingleServerIRCBot (using with twitch) 【发布时间】:2018-10-31 05:12:20 【问题描述】:

向包含SingleServerIRCBot 的线程发送断开信号的正确方法是什么?

我正在实例化连接到 twitch 的机器人

import threading
import irc.bot
class MyBot(irc.bot.SingleServerIRCBot):
    ...
bot = MyBot(...)
threads = []
t = threading.Thread(target=bot.start()
threads.append(t)
t.start()

当流不再存在时,无论我如何尝试,我都无法让线程成功结束。我应该如何向线程发送信号,告诉它退出通道杀死机器人然后杀死它自己?

.start 方法的代码可以在这里找到https://github.com/jaraco/irc/blob/master/irc/bot.py#L331

我的第一个想法是使用具有退出条件的 while 循环覆盖该方法。到目前为止,我还没有运气。

此外,https://github.com/jaraco/irc/blob/master/irc/bot.py#L269 这里有一个.die 方法,但是当线程正在执行无限循环时,如何调用该方法?

尝试直接杀死线程最终导致它们持续存在,并最终引发有关我的进程正在运行的线程总数的错误。

为赏金编辑:我也会接受一个描述一次处理多个 IRC 机器人的更好方法的答案。

【问题讨论】:

【参考方案1】:

我认为您不能(或不应该)直接终止线程,但您可以停止在该线程上运行的任务。然后线程将处于非活动状态,如果您愿意,您可以将其从线程列表中删除。我不熟悉SingleServerIRCBot,但我将使用下面的类作为示例。

import time

class MyTask:
    def __init__(self):
        self._active = True

    def start(self):
        while self._active:
            print('running')
            time.sleep(1)

    def die(self):
        self._active = False

在 Python3 中,线程有一个_target 属性,我们可以从中访问目标函数/方法。我们可以使用该属性访问目标的对象并调用die 方法(例如:thread._target.__self__.die())。不过我认为最好将Thread 子类化并将目标对象存储在一个变量中,因为_target 是一个私有属性,也是出于兼容性原因。

import threading

class MyThread(threading.Thread):
    def __init__(self, target, args=()):
        super(MyThread, self).__init__()
        self.target = target
        self.args = args

    def run(self):
        self.target.start(*self.args)

    def stop_task(self):
        self.target.die()

使用这个类,我们将传递一个MyTask 对象作为目标,并从MyThread.run 调用start 方法。现在我们可以使用MyThread.stop_task 来停止在这个线程上运行的任务。

o = MyTask()
t = MyThread(target=o)
t.start()
t.stop_task()
time.sleep(1.1)
print(t.is_alive())

请注意,我正在等待 1.1 秒来测试线程是否处于活动状态。这是因为目标 (MyTask.start) 最多需要一秒钟才能停止。此方法不会杀死线程,而是调用MyTask.die 并等待任务完成。如果您想立即结束任务(并释放任务使用的任何资源),您可以使用Process 并以.terminate 结束它。如果您的任务执行的 CPU 操作多于 IO 操作,您还应该选择多处理而不是多线程,因为进程不受GIL 的限制。


在研究源代码之后,我注意到.die() 调用sys.exit,所以我们不能用它来终止任务,因为它会停止程序。原因似乎是.start() 调用了父对象的.start(),然后调用了Reactor 对象的.process_forever() 方法。此方法开始在没有中断条件的无限循环中运行Reactor.process_once()

一种可能的解决方案是继承SingleServerIRCBot 并使用布尔变量来中断循环。此类应覆盖.start().die(),以阻止机器人在线程上运行。 .die() 方法会将标志设置为 false,.start() 将在循环中调用 Reactor.process_once()

import irc.bot

class MyBot(irc.bot.SingleServerIRCBot):
    def __init__(self, channel, nickname, server, port=6667):
        super(MyBot, self).__init__([(server, port)], nickname, nickname)
        self.channel = channel
        self._active = True

    def start(self):
        self._connect()
        while self._active:
            self.reactor.process_once(timeout=0.2)

    def die(self, msg="Bye, cruel world!"):
        self.connection.disconnect(msg)
        self._active = False

现在我们可以通过在运行机器人的线程上调用.stop_task() 或直接调用机器人的.die() 方法来停止机器人。

host, port = 'irc.freenode.net', 6667
nick = 'My-Bot'
channel = '#python'

bot = MyBot(channel, nick, host, port)
t = MyThread(bot)
t.start()
t.stop_task()
#bot.die()

【讨论】:

不幸的是,我不确定如何覆盖该类中的 start 方法。我不完全确定它是如何工作的。此外,我不能使用多处理,因为我需要在线程之间传递无法腌制的对象。 我并不是建议你重写SingleServerIRCBot.start 方法,我使用的MyTask 类只是一个例子。我的意思是你可以继承Thread,这样你就可以从它运行的线程中控制SingleServerIRCBot对象——调用MyThread.stop_task会调用机器人的die方法。如果机器人主要执行网络 IO 操作,则不需要多处理。 事实证明你可能不得不覆盖.start().die(),因为.die()调用sys.exit。我会更新一个可能的解决方案,但是我还没有安装这个模块,所以我无法测试它是否有效。 感谢您对此的帮助。我决定继续从头开始编写我自己的 irc 客户端,这样我就可以更轻松地访问事件循环。不过,我会继续为您提供奖励:) 感谢您的赏金,但如果可以的话,我想帮助解决这个问题。我已经测试了 tis 代码,它似乎可以工作——这意味着它会停止运行机器人的线程。如果您能提供一些反馈,我可能会提供更多帮助。干杯!

以上是关于使用 irc.bot.SingleServerIRCBot 持续存在的线程(与 twitch 一起使用)的主要内容,如果未能解决你的问题,请参考以下文章

在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?

今目标使用教程 今目标任务使用篇

Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)

MySQL db 在按日期排序时使用“使用位置;使用临时;使用文件排序”

使用“使用严格”作为“使用强”的备份

Kettle java脚本组件的使用说明(简单使用升级使用)