如何使用计划库运行异步功能?

时间:2018-07-26 02:10:03

标签: python python-3.x async-await schedule discord.py-rewrite

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

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())

使用schedule.every().day.at("00:00").do()函数,将await send_channel()放在.do()的参数中时会出现此错误:

  

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

但是当我不使用await且仅将send_channel()作为参数时,会出现此错误:

  

RuntimeWarning:从未等待协程'send_channel'

我不是很擅长编程,所以如果有人可以为我愚弄它,那将很棒。

谢谢

5 个答案:

答案 0 :(得分:3)

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

from discord.ext import commands, tasks
from datetime import datetime, timdelta

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()

答案 1 :(得分:2)

另一个选择是使用apscheduler的AsyncIOScheduler,它与异步功能(例如send_channel)配合使用更为自然。就您而言,您可以简单地编写以下形式的内容:

scheduler = AsyncIOScheduler()
scheduler.add_job(send_channel, trigger=tr)
scheduler.start()

其中tr是触发对象。您可以使用IntervalTrigger(间隔为1天,开始日期为21:57),也可以使用CronTrigger

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

答案 2 :(得分:1)

经历同样的问题时,我发现了一个解决方案,其中混合了一些以前的解决方案:

import schedule
from discord.ext import tasks

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

,然后在我定义的主线程之前

def event_starter(func):
    if not func.is_running():
        func.start()

schedstop = threading.Event()
def timer():
    while not schedstop.is_set():
        schedule.run_pending()
        sleep(1)
schedthread = threading.Thread(target=timer)
schedthread.start()

最后,在主线程中:

if __name__ == "__main__":

    ...

    schedule.every().day.at('21:57:00').do(event_starter, send_channel)

    ...

答案 3 :(得分:0)

您正在做的事情行不通,因为info.udpate → info.update 使用了函数(或另一个可调用的函数),但是您正在尝试do或调用一个函数,然后将结果传递给它。

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

如果仅通过send_channel()传递了一个函数,但这是一个send_channel协程函数,ascynio不知道如何运行。


此外,与其尝试将schedule集成到schedule事件循环中,而且不弄清楚如何将异步作业包装为asyncio任务,反之亦然,等等,仅给schedule自己的线程就容易得多。

There's a FAQ entry on this

  

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

     

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

基本思想很简单。将您的schedule函数更改为此:

timer

在程序开始之前,甚至在开始schedstop = threading.Event() def timer(): while not schedstop.is_set(): schedule.run_pending() time.sleep(3) schedthread = threading.Thread(target=timer) schedthread.start() 事件循环之前,请执行此操作。

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

asyncio

现在,要添加任务,无论您是在顶级代码中,在异步协程中还是在schedstop.set() 任务中,都只需这样添加即可:< / p>

scheduler

现在,回到您的第一个问题。您要运行的任务不是正常功能,而是一个schedule.every().day.at("21:57").do(task) 协程,它必须作为主事件循环的一部分在主线程上运行。

但这就是call_soon_threadsafe的用途。您要呼叫的是:

asyncio

要让bot.loop.call_soon_threadsafe(send_channel) 运行它,您只需传递scheduler作为函数,并传递bot.loop.call_soon_threadsafe作为参数。

因此,将它们放在一起:

send_channel

答案 4 :(得分:0)

这是一个古老的问题,但是我最近遇到了同样的问题。您可以使用run_coroutine_threadsafe将协程安排到事件循环中(而不是回调):

{{1}}