función de fondo en Python

88

Tengo un script de Python que a veces muestra imágenes al usuario. Las imágenes pueden, a veces, ser bastante grandes y se reutilizan con frecuencia. Mostrarlos no es crítico, pero mostrar el mensaje asociado con ellos sí lo es. Tengo una función que descarga la imagen necesaria y la guarda localmente. En este momento, se ejecuta en línea con el código que muestra un mensaje al usuario, pero eso a veces puede llevar más de 10 segundos para imágenes no locales. ¿Hay alguna forma de llamar a esta función cuando sea necesario, pero ejecutarla en segundo plano mientras el código continúa ejecutándose? Solo usaría una imagen predeterminada hasta que esté disponible la correcta.

Dan Hlavenka
fuente

Respuestas:

129

Haz algo como esto:

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

luego en línea, haz algo como esto:

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

Es posible que desee verificar si el hilo ha terminado antes de continuar con otras cosas llamando download_thread.isAlive()

TorelTwiddler
fuente
El intérprete permanece abierto hasta que se cierra el hilo. (ejemplo import threading, time; wait=lambda: time.sleep(2); t=threading.Thread(target=wait); t.start(); print('end')). Tenía la esperanza de que "antecedentes" también implicara desapego.
ThorSummoner
3
@ThorSummoner Los subprocesos están todos contenidos en el mismo proceso. Si está buscando generar un nuevo proceso, querrá buscar en los módulos subprocesso multiprocessingpython en su lugar.
TorelTwiddler
@TorelTwiddler Quiero ejecutar una función en segundo plano, pero tengo algunas limitaciones de recursos y no puedo ejecutar la función tantas veces como quiero y quiero poner en cola las ejecuciones adicionales de la función. ¿Tienes alguna idea de cómo debería hacer eso? Aquí tengo mi pregunta . ¿Podría echar un vistazo a mi pregunta? ¡Cualquier ayuda sería genial!
Amir
3
Si desea múltiples parámetros: download_thread = threading.Thread (target = function_that_downloads, args = (variable1, variable2, variableN))
georgeos
cómo pasar múltiples argumentos - al igual que el método es add(a,b)y obtener valor de retorno de ese método
Maifee Ul Asad
7

Normalmente, la forma de hacer esto sería usar un grupo de subprocesos y descargas en cola que emitirían una señal, también conocida como evento, cuando esa tarea haya terminado de procesarse. Puede hacer esto dentro del alcance del módulo de subprocesos que proporciona Python.

Para realizar dichas acciones, usaría objetos de eventos y el módulo Queue .

Sin embargo, a continuación se puede ver una demostración rápida y sucia de lo que puede hacer con una threading.Threadimplementación simple :

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

Probablemente tendría sentido no sondear como lo estoy haciendo arriba. En cuyo caso, cambiaría el código a esto:

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

Tenga en cuenta que no hay ningún indicador de demonio establecido aquí.

Mahmoud Abdelkader
fuente
4

Prefiero usar gevent para este tipo de cosas:

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

Todo se ejecuta en un hilo, pero siempre que se bloquea una operación del núcleo, gevent cambia de contexto cuando hay otros "greenlets" en ejecución. Las preocupaciones sobre el bloqueo, etc. se reducen mucho, ya que solo hay una cosa ejecutándose a la vez, sin embargo, la imagen continuará descargándose siempre que se ejecute una operación de bloqueo en el contexto "principal".

Dependiendo de cuánto y qué tipo de cosas quiera hacer en segundo plano, esto puede ser mejor o peor que las soluciones basadas en subprocesos; ciertamente, es mucho más escalable (es decir, puede hacer muchas más cosas en segundo plano), pero eso podría no ser motivo de preocupación en la situación actual.

Shaunc
fuente
cuál es el propósito de esta línea from gevent import monkey; monkey.patch_all():?
nz_21
biblioteca estándar de parches para compatibilidad con gevent
gevent.org/api/gevent.monkey.html