Exécution de run_coroutine_threadsafe dans un thread séparé

Oct 15 2020

J'ai un script qui s'exécute constamment pour toujours (il vérifie les changements dans les fichiers). J'ai besoin d'envoyer des messages Discord chaque fois qu'un fichier étrange est créé.

  • Le problème est que la fonction d'observation des événements ( def run(self):ci-dessous) provient d'une sous-classe, je ne peux donc pas la changer en async def run(self):. Par conséquent, je ne peux pas utiliserawait channel.send()
  • Ma solution à cela était d'utiliser run_coroutine_threadsafecomme expliqué ici:https://stackoverflow.com/a/53726266/9283107. Cela fonctionne bien! Mais le problème est que les messages sont placés dans une file d'attente et ne sont jamais envoyés tant que ce script n'est pas terminé (ce qui dans mon cas serait: jamais). Je suppose que les fonctions d'envoi de message sont placées dans le fil de discussion sur lequel ce script est activé, donc le fil n'y parvient jamais?

Peut-être pouvons-nous jeter le run_coroutine_threadsafedans un fil séparé ou quelque chose? C'est l'exemple le plus minimal que je puisse faire qui montre encore mon problème de sous-classe.

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

Réponses

2 user4815162342 Oct 15 2020 at 11:05

Tout d'abord, notez que vous n'êtes pas autorisé à appeler un code de blocage tel que time.sleep()depuis un fichier async def. Pour démarrer une fonction de blocage et la faire communiquer avec asyncio, vous pouvez créer un thread d'arrière-plan depuis on_readyou même depuis le niveau supérieur, comme ceci:

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

Votre thread principal exécutera la boucle d'événements asyncio et votre thread d'arrière-plan vérifiera les fichiers, en utilisant asyncio.run_coroutine_threadsafe()pour communiquer avec asyncio et discord.

Comme indiqué dans un commentaire sous la réponse à laquelle vous avez lié, asyncio.run_coroutine_threadsafesuppose que vous avez plusieurs threads en cours d'exécution (donc "thread-safe"), dont l'un exécute la boucle d'événements. Jusqu'à ce que vous implémentiez cela, toute tentative d'utilisation asyncio.run_coroutine_threadsafeéchouera.

2 Frank Oct 15 2020 at 10:59

Suite aux user4815162342commentaires sur la question, je suis venu avec ceci, qui fonctionne parfaitement!

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