Executando run_coroutine_threadsafe em um thread separado
Eu tenho um script que está constantemente em execução para sempre (ele verifica as alterações nos arquivos). Eu preciso enviar mensagens Discord sempre que um arquivo estranho é feito.
- O problema é que a função de observação de eventos (
def run(self):abaixo) é de uma subclasse, então não posso alterá-la paraasync def run(self):. Portanto não posso usarawait channel.send() - Minha solução para isso foi usar
run_coroutine_threadsafecomo explicado aqui:https://stackoverflow.com/a/53726266/9283107. Isso funciona bem! Mas o problema é que as mensagens são colocadas em uma fila e nunca são enviadas até que esse script termine (que no meu caso seria: nunca). Presumo que as funções de envio de mensagem sejam colocadas no segmento em que este script está, portanto, o segmento nunca chega até eles?
Talvez possamos lançar o run_coroutine_threadsafeem um segmento separado ou algo assim? Este é o exemplo mínimo que posso fazer e que ainda mostra meu problema de subclasse.
import discord
import os
import asyncio
import time
# CHANNEL_ID = 7659170174????????
client = discord.Client()
channel = None
class Example():
# Imagine this run comes from a subclass, so you can't add sync to it!
def run(self):
# await channel.send('Test') # We can't do this because of the above comment
asyncio.run_coroutine_threadsafe(channel.send('Test'), _loop)
print('Message sent')
@client.event
async def on_ready():
print('Discord ready')
global channel
channel = client.get_channel(CHANNEL_ID)
for i in range(2):
Example().run()
time.sleep(3)
print('Discord messages should appear by now. Sleeping for 20s to give it time (technically this would be infinite)')
time.sleep(20)
print('Script done. Now they only get sent for some reason')
_loop = asyncio.get_event_loop()
client.run('Your secret token')
Respostas
Primeiro, observe que você não tem permissão para chamar códigos de bloqueio, como time.sleep()de um async def. Para iniciar uma função de bloqueio e fazer com que ela se comunique com asyncio, você pode gerar um thread em segundo plano on_readyou até mesmo do nível superior, como este:
# checker_function is the function that blocks and that
# will invoke Example.run() in a loop.
threading.Thread(
target=checker_function,
args=(asyncio.get_event_loop(), channel)
).start()
Seu encadeamento principal executará o loop de evento de assíncio e seu encadeamento de segundo plano verificará os arquivos, usando asyncio.run_coroutine_threadsafe()para se comunicar com assíncio e discórdia.
Conforme apontado em um comentário sob a resposta que você vinculou, asyncio.run_coroutine_threadsafeassume que você tem vários threads em execução (portanto, "thread-safe"), um dos quais executa o loop de eventos. Até que você implemente isso, qualquer tentativa de uso asyncio.run_coroutine_threadsafefalhará.
Seguindo os user4815162342comentários sobre a questão, eu vim com isso, que funciona perfeitamente!
import discord
import os
import asyncio
import time
import threading
CHANNEL_ID = 7659170174????????
client = discord.Client()
channel = None
class Example():
# Imagine this run comes from a subclass, so you can't add sync to it!
def run(self):
# await channel.send('Test') # We can't do this because of the above comment
asyncio.run_coroutine_threadsafe(channel.send('Tester'), _loop)
print('Message sent')
def start_code():
for i in range(2):
Example().run()
time.sleep(20)
@client.event
async def on_ready():
print('Discord ready')
global channel
channel = client.get_channel(CHANNEL_ID)
threading.Thread(target=start_code).start()
_loop = asyncio.get_event_loop()
client.run('Your secret token')