英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论