如何在单独的线程内的同步函数中向用户发送消息?

huangapple go评论89阅读模式
英文:

How to send message to user in a syncronous function inside a separate thread?

问题

I'm currently working on a telegram bot. I want to create a game which will act fully like a CLI game, but you need to send messages to telegram bot to play it. And I ran into one problem, my game works on an infinite while loop and to run that game in a non-blocking manner I decided to put it in a separate thread. The problem is that context.bot.send_message(...) doesn't send any messages in a thread and I suspect that it's because context.bot.send_message(...) is async and is being called from a sync context.

Here's the code I wrote:

async def start_game_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if context.user_data.get("game") is not None:
        await context.bot.send_message(chat_id=update.effective_chat.id, parse_mode="markdown", text="Game has already started!")
        return 

    game = Game()

    context.user_data["game"] = game
    
    game_process = threading.Thread(
        target=game.start,
        args=(
            functools.partial( 
                context.bot.send_message, # this is basically a `plugin_func()` from the next code block
                chat_id=update.effective_chat.id,
                parse_mode="markdown",
                text=f"```text {context.user_data['game'].get_graphics()}```"
            ),
        )
    )

    game_process.start()

This function starts the game and passes the function context.bot.send_message(...) to the thread so that it could be plugged in inside the main while loop of the game.
And here's a start function itself:

class Game:
    ...

    def start(self, plugin_func: Callable) -> None:
        self._game_started = True

        while self._game_started and not self._game_finished:
            self.__update_player_pos()
            self.__update_surroundings_position()
            plugin_func() # This is the place where it gives a warning
            print(self.get_graphics())
            time.sleep(self.game_pacing)

And this is the warning message I get:

RuntimeWarning: coroutine 'ExtBot.send_message' was never awaited
  plugin_func()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

The problem is that the game starts and print(self.get_graphics()) prints out current state of the game perfectly. But the plugin_func does not send any messages in my telegram bot.

I think that the problem is that the plugin_func is async and I try to call it from a sync context, but I don't want to change my start function to asynchronous so what should I do?

英文:

I'm currently working on a telegram bot. I want to create a game which will act fully like a CLI game, but you need to send messages to telegram bot to play it. And I ran into one problem, my game works on an infinite while loop and to run that game in a non-blocking manner I decided to put it in a separate thread. The problem is that context.bot.send_message(...) doesn't send any messages in a thread and I suspect that it's because context.bot.send_message(...) is async and is being called from a sync context.

Here's the code I wrote:

async def start_game_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if context.user_data.get("game") is not None:
        await context.bot.send_message(chat_id=update.effective_chat.id, parse_mode="markdown", text="Game has already started!")
        return 

    game = Game()

    context.user_data["game"] = game
    
    game_process = threading.Thread(
        target=game.start,
        args=(
            functools.partial( 
                context.bot.send_message, # this is basically a `plugin_func()` from the next code block
                chat_id=update.effective_chat.id,
                parse_mode="markdown",
                text=f"```text {context.user_data['game'].get_graphics()}```"
            ),
        )
    )

    game_process.start()

This function starts the game and passes the function context.bot.send_message(...) to the thread so that it could be plugged in inside the main while loop of the game.
And here's a start function itself:

class Game:
    ...

    def start(self, plugin_func: Callable) -> None:
        self._game_started = True

        while self._game_started and not self._game_finished:
            self.__update_player_pos()
            self.__update_surroundings_position()
            plugin_func() # This is the place where it gives a warning
            print(self.get_graphics())
            time.sleep(self.game_pacing)

And this is the warning message I get:

RuntimeWarning: coroutine 'ExtBot.send_message' was never awaited
  plugin_func()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

The problem is that the game starts and print(self.get_graphics()) prints out current state of the game perfectly. But the plugin_func does not send any messages in my telegram bot.

I think that the problem is that the plugin_func is async and I try to call it from a sync context, but I don't want to change my start function to asyncronous so what should I do?

答案1

得分: 0

这是我的main.py文件:

async def start_game_handler(update: Update, context: ContextTypes.DEFAULT_TYPE, application):
    if context.user_data.get("game") is not None:
        await context.bot.send_message(chat_id=update.effective_chat.id, parse_mode="markdown", text="游戏已经开始!")
        return 

    game = Game()

    context.user_data["game"] = game
    chat_id = update.effective_chat.id
    
    game_process = threading.Thread(
        target=game.start,
        args=(
            functools.partial(
                context.bot.send_message,
                parse_mode="markdown",
                chat_id=chat_id
            ),
        )
    )

    game_process.start()

    print(f"场景:\n{context.user_data['game'].get_graphics()}")

我的Game类:

class Game:
    ...

    def start(self, plugin_func: Callable) -> None:
        self._game_started = True
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        while self._game_started and not self._game_finished:
            self.__update_player_pos()
            self.__update_surroundings_position()
            asyncio.get_event_loop().run_until_complete(plugin_func(text=f"```text{self.get_graphics()}```"))
            print(self.get_graphics())
            time.sleep(self.game_pacing)
            
        loop.close()

因为我在单独的线程中调用了start函数,所以我需要在其中创建一个异步事件循环。如果我每次调用plugin_func时都创建一个事件循环,我认为效率不高。因此,我在while循环外创建了事件循环,并使用这个我创建的事件循环来调用plugin_func。

英文:

I did not find a solution without changing the code inside a Game class. But here's what I ended up changing (explanations after the code):

This is my main.py:

async def start_game_handler(update: Update, context: ContextTypes.DEFAULT_TYPE, application):
    if context.user_data.get("game") is not None:
        await context.bot.send_message(chat_id=update.effective_chat.id, parse_mode="markdown", text="Game has already started!")
        return 

    game = Game()

    context.user_data["game"] = game
    chat_id = update.effective_chat.id
    
    game_process = threading.Thread(
        target=game.start,
        args=(
            functools.partial(
                context.bot.send_message,
                parse_mode="markdown",
                chat_id=chat_id
            ),
        )
    )

    game_process.start()

    print(f"Field:\n{context.user_data['game'].get_graphics()}")

My Game class:

class Game:
    ...

    def start(self, plugin_func: Callable) -> None:
        self._game_started = True
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        while self._game_started and not self._game_finished:
            self.__update_player_pos()
            self.__update_surroundings_position()
            asyncio.get_event_loop().run_until_complete(plugin_func(text=f"```text{self.get_graphics()}```"))
            print(self.get_graphics())
            time.sleep(self.game_pacing)
            
        loop.close()

So because I'm calling start function inside a separate thread I need to somehow create a async event loop inside of it. And if I were to create an event loop every time my plugin_func would be called, it just wouldn't be that efficient in my opinion. So instead I created event loop outside of the while loop and called my plugin_func using this event loop that I created.

huangapple
  • 本文由 发表于 2023年8月11日 00:29:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/76877670.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定