使用 asyncio 运行协程后如何继续原始事件循环?

Posted

技术标签:

【中文标题】使用 asyncio 运行协程后如何继续原始事件循环?【英文标题】:How do you continue with the original event loop after running a coroutine using asyncio? 【发布时间】:2021-05-19 08:39:15 【问题描述】:

我正在创建一个 python 脚本,它基本上从网页中检索信息并将消息发送到不和谐服务器。

我有一个主要的文件

import asyncio
import discord
from dotenv import load_dotenv

def update(message:str):
    load_dotenv()
    TOKEN = os.getenv('DISCORD_TOKEN')
    GUILD = os.getenv('DISCORD_GUILD')

    client = discord.Client()

    @client.event
    async def on_ready():
        for guild in client.guilds:
            if guild.name == GUILD:
                break

        print(
            f'client.user is connected to the following guild:\n'
            f'guild.name(id: guild.id)'
        )
        updatesChannel = client.get_channel([CHANNEL 1 ID HERE])
        await updatesChannel.send(message)
    client.run(TOKEN)

def log(message:str):
    load_dotenv()
    TOKEN = os.getenv('DISCORD_TOKEN')
    GUILD = os.getenv('DISCORD_GUILD')

    client = discord.Client()

    @client.event
    async def on_ready():
        for guild in client.guilds:
            if guild.name == GUILD:
                break

        print(
            f'client.user is connected to the following guild:\n'
            f'guild.name(id: guild.id)'
        )
        serverLogChannel = client.get_channel([CHANNEL 2 ID HERE])
        await serverLogChannel.send(message)
    client.run(TOKEN)

while True:
    text = ['Text1', 'Text2']
    channel1text = text[0]
    channel2text = text[1]
    loop = asyncio.get_event_loop()
    loop.run_until_complete( update(channel1text) )

    loop = asyncio.get_event_loop()
    loop.run_until_complete( log(channel2text) )

目前,脚本只会在第一个通道上发送消息,而不会在第二个通道上发送消息,并且不会继续进行 While 循环。我在这里遗漏了什么吗?

【问题讨论】:

client.get_channel(Channel) 应该如何在一个函数中返回 updatesChannel 而在另一个函数中返回 serverLogChannel?你在哪里定义Channel 抱歉,Channel 只是频道 ID 的占位符,它是硬编码的。将更新问题以使其更清楚。谢谢 另外,updatelog 是要一个接一个地运行还是并行运行? 一个接一个 它们是连接到相同的令牌/公会/...,正如这里所暗示的,还是连接到不同的? 【参考方案1】:

您的代码的核心问题是client.run 是一个方便的同步函数,它将运行事件循环并永远运行它,以保持客户端运行。这也是为什么您的 update 函数永远不会返回的原因。顺便说一句,将同步函数传递给run_until_complete 是行不通的,如果update 确实返回,你会得到一个异常,Python 不会继续下一行。

要解决此问题,您可以从 client.run 切换到 client.start,这是异步的。您还可以利用两个客户端都连接到同一个令牌/公会并仅使用一个客户端这一事实。这样公会的搜索只能完成一次,logupdate 函数可以简化为简单的channel.send。例如(未经测试):

async def send_message(client, channel_id, message):
    channel = client.get_channel(channel_id)
    await channel.send(message)

async def main():
    load_dotenv()
    TOKEN = os.getenv('DISCORD_TOKEN')
    GUILD = os.getenv('DISCORD_GUILD')

    client = discord.Client()
    asyncio.create_task(client.start(TOKEN))
    await client.wait_until_ready()
    for guild in client.guilds:
        if guild.name == GUILD:
            break
    print(
        f'client.user is connected to the following guild:\n'
        f'guild.name(id: guild.id)'
    )

    while True:
        text = ['Text1', 'Text2']
        channel1text = text[0]
        channel2text = text[1]
        await send_message(client, [CHANNEL 1 ID], channel1text)
        await send_message(client, [CHANNEL 2 ID], channel2text)

if __name__ == '__main__':
    asyncio.run(main())

【讨论】:

以上是关于使用 asyncio 运行协程后如何继续原始事件循环?的主要内容,如果未能解决你的问题,请参考以下文章

异步IO框架:asyncio 中篇

Asyncio之EventLoop笔记

Python - 如何使用 asyncio 同时运行多个协程?

如何将协程添加到正在运行的 asyncio 循环中?

asyncio 异步协程

Python asyncio run_coroutine_threadsafe 从不运行协程?