Esecuzione di run_coroutine_threadsafe in un thread separato

Oct 15 2020

Ho uno script che è costantemente in esecuzione per sempre (controlla le modifiche nei file). Devo inviare messaggi Discord ogni volta che viene creato un file strano.

  • Il problema è che la funzione di visualizzazione degli eventi ( def run(self):sotto) proviene da una sottoclasse, quindi non posso cambiarla in async def run(self):. Quindi non posso usareawait channel.send()
  • La mia soluzione a questo era usare run_coroutine_threadsafecome spiegato qui:https://stackoverflow.com/a/53726266/9283107. Funziona bene! Ma il problema è che i messaggi vengono inseriti in una coda e non vengono mai inviati fino al termine di questo script (che nel mio caso sarebbe: mai). Presumo che le funzioni di invio del messaggio vengano inserite nel thread su cui si trova questo script, quindi il thread non le arriva mai?

Forse possiamo inserire il run_coroutine_threadsafein un thread separato o qualcosa del genere? Questo è l'esempio più minimale che posso fare che mostra ancora il mio problema di sottoclasse.

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

Risposte

2 user4815162342 Oct 15 2020 at 11:05

Innanzitutto, tieni presente che non puoi chiamare codice di blocco come time.sleep()da un file async def. Per avviare una funzione di blocco e farla comunicare con asyncio, puoi generare un thread in background da on_readyo anche dal livello superiore, come questo:

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

Il thread principale eseguirà il ciclo di eventi asyncio e il thread in background controllerà i file, utilizzando asyncio.run_coroutine_threadsafe()per comunicare con asyncio e discord.

Come sottolineato in un commento sotto la risposta a cui ti sei collegato, asyncio.run_coroutine_threadsafepresume che tu abbia più thread in esecuzione (quindi "thread-safe"), uno dei quali esegue il ciclo di eventi. Fino a quando non lo implementerai, qualsiasi tentativo di utilizzo asyncio.run_coroutine_threadsafefallirà.

2 Frank Oct 15 2020 at 10:59

Dopo aver user4815162342commentato la domanda, mi è venuta in mente questo, che funziona perfettamente!

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