fonction d'arrière-plan en Python

88

J'ai un script Python qui affiche parfois des images à l'utilisateur. Les images peuvent parfois être assez grandes et elles sont souvent réutilisées. Les afficher n'est pas critique, mais afficher le message qui leur est associé l'est. J'ai une fonction qui télécharge l'image nécessaire et l'enregistre localement. À l'heure actuelle, il est exécuté en ligne avec le code qui affiche un message à l'utilisateur, mais cela peut parfois prendre plus de 10 secondes pour les images non locales. Existe-t-il un moyen d'appeler cette fonction quand c'est nécessaire, mais de l'exécuter en arrière-plan pendant que le code continue de s'exécuter? J'utiliserais simplement une image par défaut jusqu'à ce que la bonne devienne disponible.

Dan Hlavenka
la source

Réponses:

129

Faites quelque chose comme ceci:

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

puis en ligne, faites quelque chose comme ceci:

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

Vous voudrez peut-être vérifier si le fil est terminé avant de passer à autre chose en appelant download_thread.isAlive()

TorelTwiddler
la source
L'interpréteur reste ouvert jusqu'à ce que le thread se ferme. (exemple import threading, time; wait=lambda: time.sleep(2); t=threading.Thread(target=wait); t.start(); print('end')). J'espérais que "l'arrière-plan" impliquait également un détachement.
ThorSummoner
3
Les threads @ThorSummoner sont tous contenus dans le même processus. Si vous cherchez à créer un nouveau processus, vous voudrez plutôt vous pencher sur les modules subprocessou multiprocessingpython.
TorelTwiddler
@TorelTwiddler Je veux exécuter une fonction en arrière-plan mais j'ai des limitations de ressources et je ne peux pas exécuter la fonction autant de fois que je le souhaite et que je veux mettre en file d'attente les exécutions supplémentaires de la fonction. Avez-vous une idée de la façon dont je dois faire cela? J'ai ma question ici . Pourriez-vous s'il vous plaît jeter un oeil à ma question? Toute aide est la bienvenue!
Amir
3
Si vous voulez plusieurs paramètres: download_thread = threading.Thread (target = function_that_downloads, args = (variable1, variable2, variableN))
georgeos
comment passer plusieurs arguments - comme la méthode est add(a,b)et obtenir la valeur de retour de cette méthode
Maifee Ul Asad
7

En règle générale, la façon de procéder serait d'utiliser un pool de threads et des téléchargements de file d'attente qui émettraient un signal, c'est-à-dire un événement, lorsque cette tâche a terminé son traitement. Vous pouvez le faire dans le cadre du module de threading fourni par Python.

Pour effectuer ces actions, j'utiliserais des objets événement et le module File d'attente .

Cependant, une démonstration rapide et sale de ce que vous pouvez faire en utilisant une threading.Threadimplémentation simple peut être vue ci-dessous:

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

Il serait probablement logique de ne pas interroger comme je le fais ci-dessus. Dans ce cas, je changerais le code en ceci:

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

Notez qu'il n'y a pas d'indicateur de démon défini ici.

Mahmoud Abdelkader
la source
4

Je préfère utiliser gevent pour ce genre de chose:

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

Tout fonctionne dans un thread, mais chaque fois qu'une opération du noyau se bloque, gevent change de contexte quand il y a d'autres "greenlets" en cours d'exécution. Les soucis concernant le verrouillage, etc. sont beaucoup plus faibles, car il n'y a qu'une seule chose en cours d'exécution à la fois, mais l'image continuera à se télécharger chaque fois qu'une opération de blocage s'exécute dans le contexte "principal".

Selon la quantité et le type de chose que vous voulez faire en arrière-plan, cela peut être meilleur ou pire que les solutions basées sur les threads; il est certainement beaucoup plus évolutif (c'est-à-dire que vous pouvez faire beaucoup plus de choses en arrière-plan), mais cela peut ne pas être préoccupant dans la situation actuelle.

shaunc
la source
quel est le but de cette ligne from gevent import monkey; monkey.patch_all():?
nz_21
correctifs bibliothèque standard pour la compatibilité gevent
gevent.org/api/gevent.monkey.html