Запуск асинхронного цикла событий с n задачами из функции синхронизации
В моем веб-приложении Django есть метод, который отправляет всем подписчикам уведомление по электронной почте всякий раз, когда создается новая запись в блоге. Выполнение этого метода отнимает много времени, и я пытаюсь сделать его более эффективным с помощью библиотеки asyncio в python. Моим мотивом является не только производительность, но и то, что мой веб-сервер приостанавливает отправку любых форм, которые занимают более 300 секунд (включая форму, которая создает новые записи в блоге и, следовательно, отправляет письма всем подписчикам). Я полагаю, что неразумно делать окно таймаута намного длиннее этого, и я знаю, что выполнение этого метода будет занимать тем больше времени, чем больше у меня объектов подписчиков. Поэтому я думаю, что нужно использовать какое-то асинхронное решение. В общем, хватит бредить. Вот что я пробовал:
# This method gets called every time a new blog post (agpost) is saved for the first time.
def send_subs_new_post_email(agpost):
logger.info("Setting up async emailing loop...")
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(setup_async_sub_email_loop(agpost, loop))
loop.close()
# Add a send_email task in the event loop for every subscriber.
async def setup_async_sub_email_loop(agpost, loop):
for i, sub in enumerate(Subscriber.objects.all()):
domain = Site.objects.get_current().domain
absolute_path = 'https://' + str(format(domain))
# Build unsubscribe link
token = encrypt(sub.email + "-" + timezone.now().today().strftime("%Y%m%d"))
unsub_confirmation = urljoin(absolute_path, reverse('homepage:unsub_confirmation') + "?token=" + token)
# Build post link
post_url = urljoin(absolute_path, reverse('posts:show', kwargs={'slug': agpost.slug}))
data = dict()
data["desc"] = agpost.desc
data["body"] = agpost.body
data["post_url"] = post_url
data["unsubscribe_url"] = unsub_confirmation
template = get_template("email_templates/post_email.html")
# Render email text (html and plain) and set subject
html_content = template.render(data)
text_content = strip_tags(html_content)
subject = agpost.title
email = sub.email
# Attempt to send notification email
logging.info("Trying to send notification email for post " + subject + " to sub " + email + "...")
loop.create_task(try_to_send_post_notification(email, subject, html_content, text_content), name=("task" + str(i)))
await asyncio.sleep(0)
# Send notification email, log the results.
def try_to_send_post_notification(email, subject, html_content, text_content):
if send_email(email, subject, html_content, text_content):
logger.info("SUCCESS: Notification sent to sub " + email + " for post " + subject)
else:
logger.error(
"ERROR: Notification FAILED to send to sub " + email + " for post " + subject)
# Send email.
def send_email(email, subject, html_content, text_content):
# Send email with subject as both html and plain text
try:
send_mail(subject, text_content, None, [email], html_message=html_content, fail_silently=True)
return True
except SMTPException as e:
logging.error("There was an SMTPException sending a user email: ", e)
return False
Когда я создаю и сохраняю блогпост и запускаю этот метод, я получаю ошибку "You cannot call this from an async context - use a thread or sync_to_async." Я попробовал украсить верхний метод "@sync_to_async", но это, похоже, не помогло. Я также заметил, что практически во всех примерах, которые я видел, цикл async объявляется и выполняется вне любого метода. Это меня смущает, и я искал, почему так происходит. Я хочу, чтобы этот цикл создавался и запускался только тогда, когда я начинаю рассылать электронные письма. Зачем мне запускать цикл вне метода, который его использует? Я уверен, что здесь я совершаю много ошибок новичка в async. Я просто пока не смог найти ответы. Любое руководство будет оценено по достоинству, спасибо огромное за ваше время