Twisted Python IRC бот - как выполнить функцию асинхронно, чтобы она не блокировала бота? - PullRequest
0 голосов
/ 08 февраля 2019

Я пытаюсь написать IRC-бота, который продолжает нормально работать, пока выполняет длинную (более 10 секунд) функцию.

Я начал с написания бота с помощью сокета.Когда я вызвал «блокирующую» функцию (вычисление, для выполнения которой требуется несколько секунд), бот, естественно, перестал отвечать и не записывал никаких сообщений, отправленных в чат, пока функция выполняла вычисления.

Я немного погуглил и увидел,многие люди рекомендуют использовать Twisted.

Я реализовал базовый бот IRC, в значительной степени основанный на некоторых примерах:

# twisted imports
from twisted.words.protocols import irc
from twisted.internet import reactor, protocol
from twisted.python import log

# system imports
import time, sys, datetime

def a_long_function():
    time.sleep(180)
    print("finished")

class BotMain(irc.IRCClient):

    nickname = "testIRC_bot"

    def connectionMade(self):
        irc.IRCClient.connectionMade(self)

    def connectionLost(self, reason):
        irc.IRCClient.connectionLost(self, reason)

    # callbacks for events

    def signedOn(self):
        """Signed to server"""
        self.join(self.factory.channel)

    def joined(self, channel):
        """Joined channel"""

    def privmsg(self, user, channel, msg):
        """Received message"""
        user = user.split('!', 1)[0]

        if 'test' in msg.lower():
            print("timeout started")

            a_long_function()

            msg = "test finished"
            self.msg(channel, msg)

        if 'ping' in msg.lower():
            self.msg(channel, "pong")
            print("pong")

class BotMainFactory(protocol.ClientFactory):
    """A factory for BotMains """

    protocol = BotMain

    def __init__(self, channel, filename):
        self.channel = channel
        self.filename = filename

    def clientConnectionLost(self, connector, reason):
        """Try to reconnect on connection lost"""
        connector.connect()

    def clientConnectionFailed(self, connector, reason):
        print ("connection failed:", reason)
        reactor.stop()

if __name__ == '__main__':

    log.startLogging(sys.stdout)
    f = BotMainFactory("#test", "log.txt")
    reactor.connectTCP("irc.freenode.net", 6667, f)
    reactor.run()

Этот подход определенно лучше, чем моя более ранняя реализация сокетов, потому что теперь ботпо-прежнему получает сообщения, отправленные во время выполнения a_long_function () .

Однако эти сообщения «видят» только после завершения функции.Это означает, что когда я регистрировал сообщения в txt-файле, все сообщения, полученные при выполнении a_long_function () , получали одинаковую отметку времени завершения функции, а не когда они фактически были отправлены в чате.

Кроме того, бот по-прежнему не может отправлять сообщения во время выполнения длинной функции.

Может ли кто-нибудь указать мне правильное направление, как мне следует изменить код так,что эта длинная функция может быть выполнена асинхронно, так что бот все еще может регистрировать и отвечать на сообщения во время выполнения?

Заранее спасибо.

Редактировать: я сталкивался с этим ответ, который дал мне идею, что я мог бы добавить deferLater вызовов в мои a_long_function , чтобы разделить его на более мелкие куски (например, чтобы выполнить 1 с), и получить возобновление работы ботаобычная работа между ответами и регистрацией любых сообщений, которые были отправлены на канал IRC за это время.Или, возможно, добавить таймер, который подсчитывает, как долго работает a_long_function , и, если он длиннее порога, он будет вызывать deferLater , чтобы позволить боту догнать буферизованные сообщения.

Это похоже на хакерскую мысль - есть ли более элегантное решение?

Ответы [ 2 ]

0 голосов
/ 08 февраля 2019

Для асинхронного вызова функции вы должны использовать пакет asyncio вместе с async / await или сопрограммами.Помните, что вызов async / await - это реализация v3, а не v2.

Использование async / await:

#!/usr/bin/env python3
# countasync.py

import asyncio

async def count():
    print("One")
    await asyncio.sleep(1)
    print("Two")

async def main():
    await asyncio.gather(count(), count(), count())

if __name__ == "__main__":
    import time
    s = time.perf_counter()
    asyncio.run(main())
    elapsed = time.perf_counter() - s
    print(f"{__file__} executed in {elapsed:0.2f} seconds.")

Существует действительно хороший учебник, который вы можете прочитать здесь , что исчерпывает использование asyncio, в глубину.

Надежда на помощь!

0 голосов
/ 08 февраля 2019

Нет, на самом деле нет более элегантного решения.Если вы не хотите использовать многопоточность, которая может выглядеть более элегантно, но может легко привести к нестабильной работе программы.Если вы можете избежать этого, воспользуйтесь решением для отсрочки.

...