백그라운드 태스크¶

@bot.every는 인자가 없는 비동기 함수를 봇이 준비된 동안 일정에 따라 실행합니다.
import datetime as dt
import logging
import bostie
from bostie import activity
from bostie.events import TaskError
log = logging.getLogger("my-bot")
bot = bostie.Bot()
TOKYO = dt.timezone(dt.timedelta(hours=9))
@bot.every(seconds=300)
async def show_guild_count() -> None:
count = len(bot.cache.guilds())
await bot.update_presence(activities=[activity.watching(f"{count} servers")])
@bot.every(time=[dt.time(9, tzinfo=TOKYO), dt.time(21, tzinfo=TOKYO)])
async def remind() -> None:
log.info("twice a day, at 9:00 and 21:00 in Tokyo")
@bot.every(time=dt.time(0, 0))
async def midnight_utc() -> None:
log.info("every day at midnight UTC")
@bot.listen
async def task_failed(failure: TaskError) -> None:
log.error("%s failed", failure.task.callback.__name__, exc_info=failure.error)
every(seconds=300)은 봇이 준비되자마자 함수를 실행하고, 그 뒤로 300초마다 실행합니다. 일정은 밀리지 않습니다. 각 실행은 첫 실행으로부터 seconds의 다음 배수 시점에 시작하며, seconds보다 오래 걸린 실행은 겹친 틱을 건너뜁니다.
every(time=...)은 매일 정해진 한 시각, 또는 여러 시각마다 함수를 실행합니다. tzinfo가 없는 datetime.time은 UTC로 해석합니다. 위 예처럼 고정 오프셋을 주면 그 오프셋을 계속 따르고, zoneinfo.ZoneInfo를 주면 그 시간대의 일광 절약 시간 변경을 따릅니다. seconds와 time 중 하나만 넘기세요.
태스크 제어하기¶
import logging
import bostie
from bostie import Context
from bostie.checks import is_owner
log = logging.getLogger("my-bot")
bot = bostie.Bot()
@bot.every(seconds=60)
async def check_feeds() -> None:
log.info("checking the feeds, run %d", check_feeds.count)
@check_feeds.before
async def open_feeds() -> None:
log.info("feed checks started")
@check_feeds.after
async def close_feeds() -> None:
log.info("feed checks stopped")
@bot.command(checks=[is_owner])
async def feeds(ctx: Context, minutes: int | None = None) -> None:
"""Pause feed checks, or check every so many minutes."""
if minutes is None:
check_feeds.stop()
await ctx.respond("Feed checks paused.", ephemeral=True)
return
check_feeds.change_interval(seconds=minutes * 60)
check_feeds.start()
await ctx.respond(f"Checking feeds every {minutes} minutes.", ephemeral=True)
@bot.every는 Task를 반환하며, check_feeds()를 await하면 여전히 함수를 한 번 실행합니다. Task의 메서드로 봇이 실행되는 동안 일정을 바꿀 수 있습니다.
Task |
동작 |
|---|---|
stop() |
실행 중이든 실행 사이든 태스크를 취소하고, start()를 호출할 때까지 봇과 함께 시작하지 않게 합니다. 봇이 실행되기 전에 호출하면 태스크는 start()를 기다립니다 |
start() |
봇이 준비된 동안 태스크를 실행합니다. 이미 준비되었다면 지금부터 실행합니다. 실행 중인 태스크는 그대로 계속됩니다 |
restart() |
태스크를 취소하고 새로 시작하므로, 간격 태스크는 바로 실행됩니다 |
change_interval(seconds=...) 또는 change_interval(time=...) |
새 일정으로 바꿉니다. 대기는 변경 시점부터, 또는 진행 중인 실행이 끝난 시점부터 새로 시작합니다 |
count |
태스크가 마지막으로 시작된 뒤 끝난 실행 횟수 |
seconds, times |
태스크가 따르는 일정 |
@task.before, @task.after |
async def hook()을 등록합니다. 태스크가 시작될 때마다, 그리고 stop(), restart(), 라우터 제외, close() 중 어떤 이유로든 멈출 때마다 await됩니다 |
훅에서 발생한 예외는 실행 중 발생한 예외처럼 TaskError로 전송됩니다. before 훅이 예외를 일으키면 태스크는 restart()할 때까지 멈춘 상태로 남습니다.
태스크가 실행되는 시점¶
태스크는 봇이 Started를 보낼 때 시작하고, close()는 HTTP 클라이언트를 닫기 전에 태스크를 취소합니다. 나중에 포함되는 라우터에 등록된 태스크는 라우터가 포함되자마자 시작하고, 라우터가 제외되면 취소됩니다. ASGIApp만으로 제공되는 앱은 앱이 시작될 때 태스크를 시작합니다.
함수 하나는 태스크로 한 번만 등록할 수 있습니다. 같은 코드를 두 일정으로 실행하려면 함수를 두 개 등록하세요.
태스크가 예외를 일으킬 때¶
실행 중 발생한 예외는 task(예외를 일으킨 Task)와 error를 담은 TaskError 이벤트로 전송되며, 태스크는 다음 예정 시각에 다시 실행됩니다. TaskError 리스너가 없으면 bostie가 트레이스백과 함께 예외를 로그에 남깁니다.