如何使用调度库运行异步函数?

Mic*_*ard 9 python schedule python-3.x async-await discord.py-rewrite

我正在使用 discord.py rewrite 编写一个不和谐的机器人,我想每天在某个时间运行一个函数。我完全没有使用异步函数的经验,我不知道如何在不使用“await”的情况下运行一个。这只是我的一段代码,这就是为什么有些东西可能没有定义。

async def send_channel():
    try:
        await active_channel.send('daily text here')
    except Exception:
        active_channel_id = None
        active_channel = None

async def timer():
    while True:
        schedule.run_pending()
        await asyncio.sleep(3)
        schedule.every().day.at("21:57").do(await send_channel())

@bot.event
async def on_ready():
    print("Logged in as")
    print(bot.user.name)
    print(bot.user.id)
    print("------")

    bot.loop.create_task(timer())
Run Code Online (Sandbox Code Playgroud)

使用该schedule.every().day.at("00:00").do()函数,当我await send_channel()输入以下参数时出现此错误.do()

self.job_func = functools.partial(job_func, *args, **kwargs) TypeError: 第一个参数必须是可调用的

但是当我不使用 await 并且我只有send_channel()参数时,我会收到此错误:

运行时警告:从未等待过协程“send_channel”

我不太擅长编程,所以如果有人可以尝试为我简化它,那就太棒了。

谢谢

Dea*_*itz 8

另一种选择是使用apscheduler的 AsyncIOScheduler,它可以更自然地与异步函数(例如send_channel)配合使用。对于您的情况,您可以简单地编写以下形式的内容:

scheduler = AsyncIOScheduler()
scheduler.add_job(send_channel, trigger=tr)
scheduler.start()
Run Code Online (Sandbox Code Playgroud)

tr触发对象在哪里。您可以使用IntervalTrigger间隔为 1 天且开始日期为 21:57 的选项,也可以使用CronTrigger.

请注意,建议在程序结束时调用shutdown()调度程序对象。


aba*_*ert 7

您正在做的事情不起作用,因为do需要一个函数(或另一个可调用函数),但您正在尝试await或调用一个函数,然后将结果传递给它。

await send_channel()阻塞直到发送完成,然后给你None,这不是一个函数。send_channel()返回一个协程,您可以稍后等待它来做一些工作,但这也不是一个函数。

如果你只是传递它send_channel,那么,这是一个函数,但它是一个ascynio协程函数,它schedule不知道如何运行。


此外,与其尝试集成scheduleasyncio事件循环中,并弄清楚如何将异步作业包装为schedule任务,反之亦然,等等,不如只提供schedule自己的线程要容易得多。

有一个常见问题解答条目

如何在不阻塞主线程的情况下持续运行调度程序?

在单独的线程中运行调度程序。Mrwick在这里为这个问题写了一个很好的解决方案(寻找 run_continuously())。

基本思想很简单。将您的timer功能更改为:

schedstop = threading.Event()
def timer():
    while not schedstop.is_set():
        schedule.run_pending()
        time.sleep(3)
schedthread = threading.Thread(target=timer)
schedthread.start()
Run Code Online (Sandbox Code Playgroud)

在程序开始时、甚至在启动asyncio事件循环之前执行此操作。

在退出时,停止调度程序线程:

schedstop.set()
Run Code Online (Sandbox Code Playgroud)

现在,要添加任务,无论您是在顶级代码中,还是在异步协程中,还是在任务中scheduler,都没关系,只需像这样添加即可:

schedule.every().day.at("21:57").do(task)
Run Code Online (Sandbox Code Playgroud)

现在,回到你的第一个问题。您要运行的任务不是普通函数,而是一个asyncio协程,它必须作为主事件循环的一部分在主线程上运行。

但这正是目的call_soon_threadsafe。你想调用的是:

bot.loop.call_soon_threadsafe(send_channel)
Run Code Online (Sandbox Code Playgroud)

要要求scheduler运行它,您只需bot.loop.call_soon_threadsafe作为函数和send_channel参数传递即可。

所以,把它们放在一起:

schedule.every().day.at("21:57").do(
    bot.loop.call_soon_threadsafe, send_channel)
Run Code Online (Sandbox Code Playgroud)

  • 螺纹部分有必要吗?因为我无法让它工作,而且我对线程不太熟悉(我知道我应该熟悉。)就“schedule.every().day.at(“21:57”).do( bot .loop.call_soon_threadsafe, send_channel)”,那部分也不适合我。我仍然收到错误,从未等待协程“send_channel”。我认为 send_channel() 函数必须具有异步才能工作。 (2认同)

Pat*_*ugh 6

对此的内置解决方案是使用discord.ext.tasks扩展。这使您可以注册要以特定时间间隔重复调用的任务。当机器人启动时,我们将循环的开始延迟到目标时间,然后每 24 小时运行一次任务:

import asyncio
from discord.ext import commands, tasks
from datetime import datetime, timedelta

bot = commands.Bot("!")

@tasks.loop(hours=24)
async def my_task():
    ...

@my_task.before_loop
async def before_my_task():
    hour = 21
    minute = 57
    await bot.wait_until_ready()
    now = datetime.now()
    future = datetime.datetime(now.year, now.month, now.day, hour, minute)
    if now.hour >= hour and now.minute > minute:
        future += timedelta(days=1)
    await asyncio.sleep((future-now).seconds)

my_task.start()
Run Code Online (Sandbox Code Playgroud)