Потоки, сохраняющиеся с irc.bot.SingleServerIRCBot (используется с twitch) - PullRequest
0 голосов
/ 31 октября 2018

Как правильно отправить сигнал разъединения в поток, содержащий SingleServerIRCBot?

Я создаю экземпляры ботов, которые подключаются к дергаться с

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, который имеет условие выхода. Мне пока не повезло с этим.

Кроме того, здесь есть .die метод https://github.com/jaraco/irc/blob/master/irc/bot.py#L269 но как я могу вызвать этот метод, когда поток выполняет бесконечный цикл?

Попытка уничтожить потоки напрямую приводит к тому, что они сохраняются, и в конечном итоге выдают ошибки об общем количестве потоков, запущенных моим процессом.

Правка для награды: я бы также принял ответ, который описывает лучший способ обработки нескольких ботов IRC одновременно.

1 Ответ

0 голосов
/ 13 ноября 2018

Я не думаю, что вы могли (или должны) убить поток напрямую, но вы могли бы остановить задачу, выполняющуюся в этом потоке. Тогда поток будет неактивным, и вы можете удалить его из списка потоков, если хотите. Я не знаком с 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 в качестве цели, и метод start будет вызван из MyThread.run. Теперь мы можем использовать 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. Вы также должны выбрать многопроцессорность вместо многопоточности, если ваша задача выполняет больше операций с ЦП, чем операций ввода-вывода, потому что процессы не ограничены GIL .


После изучения исходного кода, я заметил, что .die() вызывает sys.exit, поэтому мы не можем использовать его для завершения задачи, потому что это остановит программу. Кажется, причина этого в том, что .start() вызывает родительский объект .start(), который затем вызывает .process_forever() метод Reactor объект. Этот метод запускает Reactor.process_once() в бесконечном цикле без условия разрыва.

Возможное решение - создать подкласс SingleServerIRCBot и использовать логическое значение variabe для разрыва цикла. Этот класс должен переопределить .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()
...