funzione di sfondo in Python


89

Ho uno script Python che a volte mostra immagini all'utente. Le immagini possono, a volte, essere piuttosto grandi e vengono riutilizzate spesso. Visualizzarli non è fondamentale, ma visualizzare il messaggio ad essi associato lo è. Ho una funzione che scarica l'immagine necessaria e la salva localmente. Al momento viene eseguito in linea con il codice che visualizza un messaggio all'utente, ma a volte ciò può richiedere più di 10 secondi per le immagini non locali. C'è un modo per chiamare questa funzione quando è necessario, ma eseguirla in background mentre il codice continua a essere eseguito? Vorrei solo utilizzare un'immagine predefinita fino a quando non diventa disponibile quella corretta.

Risposte:


130

Fai qualcosa di simile:

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

quindi in linea, fai qualcosa del genere:

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

Potresti voler controllare se il thread è terminato prima di passare ad altre cose chiamando download_thread.isAlive()


L'interprete rimane aperto fino alla chiusura del thread. (esempio import threading, time; wait=lambda: time.sleep(2); t=threading.Thread(target=wait); t.start(); print('end')). Speravo che "sfondo" implicasse anche distacco.
ThorSummoner

3
I thread @ThorSummoner sono tutti contenuti nello stesso processo. Se stai cercando di generare un nuovo processo, dovrai invece esaminare i moduli subprocesso multiprocessingpython.
TorelTwiddler

@TorelTwiddler Voglio eseguire una funzione in background ma ho alcuni limiti di risorse e non posso eseguire la funzione tutte le volte che voglio e voglio mettere in coda le esecuzioni extra della funzione. Hai qualche idea su come dovrei farlo? Ho la mia domanda qui . Potresti dare un'occhiata alla mia domanda? Qualsiasi aiuto sarebbe grande!
Amir

3
Se desideri più parametri: download_thread = threading.Thread (target = function_that_downloads, args = (variable1, variable2, variableN))
georgeos

come passare più argomenti - come il metodo add(a,b)e ottenere il valore restituito da quel metodo
Maifee Ul Asad

7

Tipicamente il modo per farlo sarebbe usare un pool di thread e download in coda che emetterebbero un segnale, noto anche come evento, quando l'attività ha terminato l'elaborazione. Puoi farlo nell'ambito del modulo di threading fornito da Python.

Per eseguire tali azioni, utilizzerei gli oggetti evento e il modulo Queue .

Tuttavia, di seguito è possibile vedere una rapida e sporca dimostrazione di ciò che è possibile fare utilizzando una semplice threading.Threadimplementazione:

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

Probabilmente avrebbe senso non sondare come sto facendo sopra. In tal caso, cambierei il codice in questo:

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

Notare che qui non è impostato alcun flag daemon.


4

Preferisco usare gevent per questo genere di cose:

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

Tutto viene eseguito in un thread, ma ogni volta che un'operazione del kernel si blocca, gevent cambia contesto quando sono in esecuzione altri "greenlet". Le preoccupazioni sul blocco, ecc. Sono molto ridotte, poiché c'è solo una cosa in esecuzione alla volta, tuttavia l'immagine continuerà a essere scaricata ogni volta che un'operazione di blocco viene eseguita nel contesto "principale".

A seconda di quanto e che tipo di cosa si desidera fare in background, questo può essere migliore o peggiore delle soluzioni basate sul threading; certamente, è molto più scalabile (cioè puoi fare molte più cose in background), ma questo potrebbe non essere un problema nella situazione attuale.


qual è lo scopo di questa riga from gevent import monkey; monkey.patch_all():?
nz_21

patch libreria standard per compatibilità gevent
gevent.org/api/gevent.monkey.html
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.