J'ai un script Python qui affiche parfois des images à l'utilisateur. Ces images peuvent parfois être très grandes et sont souvent réutilisées. Leur affichage n'est pas critique, mais l'affichage du message qui leur est associé l'est. J'ai une fonction qui télécharge l'image nécessaire et l'enregistre localement. Pour l'instant, elle est exécutée en ligne avec le code qui affiche un message à l'utilisateur, mais cela peut parfois prendre plus de 10 secondes pour des images non locales. Existe-t-il un moyen d'appeler cette fonction lorsqu'elle est nécessaire, mais de l'exécuter en arrière-plan pendant que le code continue à s'exécuter ? J'utiliserais simplement une image par défaut jusqu'à ce que la bonne image soit disponible.
Réponses
Trop de publicités?Faites quelque chose comme ça :
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
Il est possible de vérifier si le fil de discussion est terminé avant de passer à autre chose en appelant download_thread.isAlive()
Pour ce faire, on utilise généralement un pool de threads et des téléchargements de file d'attente qui émettent un signal, c'est-à-dire un événement, lorsque le traitement de la tâche est terminé. Vous pouvez faire cela dans le cadre de la fonction module de filetage Python fournit.
Pour effectuer ces actions, j'utiliserais objets événementiels et le Module de file d'attente .
Cependant, une démonstration rapide et pratique de ce que vous pouvez faire en utilisant un simple threading.Thread
est présentée 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 judicieux de ne pas faire de sondage comme je le fais ci-dessus. Dans ce cas, je modifierais le code comme suit :
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 de drapeau daemon ici.
Je préfère utiliser événement pour ce genre de choses :
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 seul thread, mais chaque fois qu'une opération du noyau se bloque, gevent change de contexte lorsqu'il y a d'autres "greenlets" en cours d'exécution. Les problèmes de verrouillage, etc. sont beaucoup moins importants, puisqu'il n'y a qu'une seule chose en cours d'exécution à la fois, mais l'image continuera à être téléchargée chaque fois qu'une opération bloquante s'exécutera dans le contexte "principal".
En fonction de la quantité et du type de choses que vous voulez faire en arrière-plan, cette solution peut être meilleure ou pire que les solutions basées sur le threading ; elle est certainement beaucoup plus évolutive (c'est-à-dire que vous pouvez faire beaucoup plus de choses en arrière-plan), mais ce n'est peut-être pas une préoccupation dans la situation actuelle.
import threading
import os
def killme():
if keyboard.read_key() == "q":
print("Bye ..........")
os._exit(0)
threading.Thread(target=killme, name="killer").start()
Si vous souhaitez ajouter d'autres clés, ajoutez def
et threading.Thread(target=killme, name="killer").start()
plusieurs fois. Cela n'est pas très esthétique mais fonctionne beaucoup mieux que des codes complexes.