Hintergrundfunktion in Python

88

Ich habe ein Python-Skript, das dem Benutzer manchmal Bilder anzeigt. Die Bilder können manchmal ziemlich groß sein und werden häufig wiederverwendet. Das Anzeigen ist nicht kritisch, das Anzeigen der ihnen zugeordneten Nachricht jedoch. Ich habe eine Funktion, die das benötigte Bild herunterlädt und lokal speichert. Momentan wird es inline mit dem Code ausgeführt, der dem Benutzer eine Nachricht anzeigt. Bei nicht lokalen Bildern kann dies jedoch manchmal mehr als 10 Sekunden dauern. Gibt es eine Möglichkeit, diese Funktion bei Bedarf aufzurufen, sie jedoch im Hintergrund auszuführen, während der Code weiterhin ausgeführt wird? Ich würde nur ein Standardbild verwenden, bis das richtige verfügbar ist.

Dan Hlavenka
quelle

Antworten:

129

Mach so etwas:

def function_that_downloads(my_args):
    # do some long download here

dann inline, mach so etwas:

import threading
def my_inline_function(some_args):
    # do some stuff
    download_thread = threading.Thread(target=function_that_downloads, name="Downloader", args=some_args)
    download_thread.start()
    # continue doing stuff

Sie können überprüfen, ob der Thread beendet ist, bevor Sie mit anderen Dingen fortfahren, indem Sie aufrufen download_thread.isAlive()

TorelTwiddler
quelle
Der Interpreter bleibt offen, bis der Thread geschlossen wird. (Beispiel import threading, time; wait=lambda: time.sleep(2); t=threading.Thread(target=wait); t.start(); print('end')). Ich hatte gehofft, dass "Hintergrund" auch losgelöst impliziert.
ThorSummoner
3
@ThorSummoner Threads sind alle im selben Prozess enthalten. Wenn Sie einen neuen Prozess erzeugen möchten, sollten Sie stattdessen die Module subprocessoder multiprocessingPython untersuchen.
TorelTwiddler
@TorelTwiddler Ich möchte eine Funktion im Hintergrund ausführen, habe jedoch einige Ressourcenbeschränkungen und kann die Funktion nicht so oft ausführen, wie ich möchte, und möchte die zusätzlichen Ausführungen der Funktion in die Warteschlange stellen. Hast du eine Idee, wie ich das machen soll? Ich habe meine Frage hier . Könnten Sie sich bitte meine Frage ansehen? Jede Hilfe wäre toll!
Amir
3
Wenn Sie mehrere Parameter möchten: download_thread = threading.Thread (target = function_that_downloads, args = (Variable1, Variable2, VariableN))
Georgeos
wie man mehrere add(a,b)
Argumente übergibt
7

In der Regel besteht die Möglichkeit hierfür darin, einen Thread-Pool und Warteschlangen-Downloads zu verwenden, die ein Signal (auch als Ereignis bezeichnet) ausgeben, wenn die Verarbeitung dieser Aufgabe abgeschlossen ist. Sie können dies im Rahmen des von Python bereitgestellten Threading-Moduls tun .

Um diese Aktionen auszuführen, würde ich Ereignisobjekte und das Warteschlangenmodul verwenden .

Eine schnelle und schmutzige Demonstration dessen, was Sie mit einer einfachen threading.ThreadImplementierung tun können, finden Sie unten:

import os
import threading
import time
import urllib2


class ImageDownloader(threading.Thread):

    def __init__(self, function_that_downloads):
        threading.Thread.__init__(self)
        self.runnable = function_that_downloads
        self.daemon = True

    def run(self):
        self.runnable()


def downloads():
    with open('somefile.html', 'w+') as f:
        try:
            f.write(urllib2.urlopen('http://google.com').read())
        except urllib2.HTTPError:
            f.write('sorry no dice')


print 'hi there user'
print 'how are you today?'
thread = ImageDownloader(downloads)
thread.start()
while not os.path.exists('somefile.html'):
    print 'i am executing but the thread has started to download'
    time.sleep(1)

print 'look ma, thread is not alive: ', thread.is_alive()

Es wäre wahrscheinlich sinnvoll, nicht wie oben zu wählen. In diesem Fall würde ich den Code folgendermaßen ändern:

import os
import threading
import time
import urllib2


class ImageDownloader(threading.Thread):

    def __init__(self, function_that_downloads):
        threading.Thread.__init__(self)
        self.runnable = function_that_downloads

    def run(self):
        self.runnable()


def downloads():
    with open('somefile.html', 'w+') as f:
        try:
            f.write(urllib2.urlopen('http://google.com').read())
        except urllib2.HTTPError:
            f.write('sorry no dice')


print 'hi there user'
print 'how are you today?'
thread = ImageDownloader(downloads)
thread.start()
# show message
thread.join()
# display image

Beachten Sie, dass hier kein Daemon-Flag gesetzt ist.

Mahmoud Abdelkader
quelle
3

Ich bevorzuge es, gevent für solche Dinge zu verwenden:

import gevent
from gevent import monkey; monkey.patch_all()

greenlet = gevent.spawn( function_to_download_image )
display_message()
# ... perhaps interaction with the user here

# this will wait for the operation to complete (optional)
greenlet.join()
# alternatively if the image display is no longer important, this will abort it:
#greenlet.kill()

Alles läuft in einem Thread, aber wenn eine Kerneloperation blockiert, wechselt gevent den Kontext, wenn andere "Greenlets" ausgeführt werden. Die Sorgen um das Sperren usw. sind stark reduziert, da jeweils nur eines ausgeführt wird. Das Image wird jedoch weiterhin heruntergeladen, wenn ein Blockierungsvorgang im "Haupt" -Kontext ausgeführt wird.

Je nachdem, wie viel und was Sie im Hintergrund tun möchten, kann dies entweder besser oder schlechter sein als Threading-basierte Lösungen. Natürlich ist es viel skalierbarer (dh Sie können viel mehr Dinge im Hintergrund tun), aber das ist in der aktuellen Situation möglicherweise nicht von Belang.

shaunc
quelle
Was ist der Zweck dieser Zeile : from gevent import monkey; monkey.patch_all()?
nz_21
Patches Standardbibliothek für Gevent-Kompatibilität - gevent.org/api/gevent.monkey.html
shaunc