Ausführen von run_coroutine_threadsafe in einem separaten Thread

Oct 15 2020

Ich habe ein Skript, das ständig für immer ausgeführt wird (es überprüft Änderungen in Dateien). Ich muss Discord-Nachrichten senden, wenn eine seltsame Datei erstellt wird.

  • Das Problem ist, dass die Ereignisüberwachungsfunktion ( def run(self):unten) aus einer Unterklasse stammt, daher kann ich sie nicht ändern async def run(self):. Daher kann ich nicht verwendenawait channel.send()
  • Meine Lösung hierfür war die Verwendung run_coroutine_threadsafewie hier erklärt:https://stackoverflow.com/a/53726266/9283107. Das funktioniert gut! Das Problem ist jedoch, dass die Nachrichten in eine Warteschlange gestellt und erst nach Abschluss dieses Skripts gesendet werden (in meinem Fall: Nie). Ich gehe davon aus, dass die Funktionen zum Senden von Nachrichten in den Thread eingefügt werden, in dem sich dieses Skript befindet. Daher gelangt der Thread nie zu ihnen.

Vielleicht können wir das run_coroutine_threadsafein einen separaten Thread werfen oder so? Dies ist das minimalste Beispiel, das ich machen kann und das immer noch mein Unterklassenproblem zeigt.

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

Antworten

2 user4815162342 Oct 15 2020 at 11:05

Beachten Sie zunächst, dass Sie keinen Blockierungscode wie z. B. time.sleep()von einem aufrufen dürfen async def. Um eine Blockierungsfunktion zu starten und sie mit Asyncio kommunizieren zu lassen, können Sie einen Hintergrund-Thread von on_readyoder sogar von der obersten Ebene wie folgt erzeugen :

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

Ihr Hauptthread führt die Asyncio-Ereignisschleife aus und Ihr Hintergrundthread überprüft die Dateien, asyncio.run_coroutine_threadsafe()um mit Asyncio und Discord zu kommunizieren.

Wie in einem Kommentar unter der Antwort, auf die Sie verlinkt haben, ausgeführt, wird asyncio.run_coroutine_threadsafedavon ausgegangen, dass mehrere Threads ausgeführt werden (daher "threadsicher"), von denen einer die Ereignisschleife ausführt. Bis Sie dies implementieren, schlägt jeder Verwendungsversuch asyncio.run_coroutine_threadsafefehl.

2 Frank Oct 15 2020 at 10:59

Nach user4815162342Kommentaren zu der Frage habe ich mir das ausgedacht, was perfekt funktioniert!

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