função de fundo em Python

88

Eu tenho um script Python que às vezes exibe imagens para o usuário. As imagens podem, às vezes, ser muito grandes e são reutilizadas com frequência. Exibi-los não é crítico, mas exibir a mensagem associada a eles é. Eu tenho uma função que baixa a imagem necessária e salva localmente. No momento, ele é executado em linha com o código que exibe uma mensagem para o usuário, mas às vezes pode levar mais de 10 segundos para imagens não locais. Existe uma maneira de chamar esta função quando necessário, mas executá-la em segundo plano enquanto o código continua a ser executado? Eu usaria apenas uma imagem padrão até que a correta estivesse disponível.

Dan Hlavenka
fonte

Respostas:

129

Faça algo assim:

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

então inline, faça algo assim:

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

Você pode querer verificar se o tópico terminou antes de prosseguir com outras coisas chamando download_thread.isAlive()

TorelTwiddler
fonte
O intérprete permanece aberto até que a discussão seja fechada. (exemplo import threading, time; wait=lambda: time.sleep(2); t=threading.Thread(target=wait); t.start(); print('end')). Eu esperava que "background" também implicasse desapego.
ThorSummoner
3
@ThorSummoner Threads estão todos contidos no mesmo processo. Se você estiver procurando iniciar um novo processo, deverá examinar os módulos subprocessou multiprocessingpython.
TorelTwiddler
@TorelTwiddler Desejo executar uma função em segundo plano, mas tenho algumas limitações de recursos e não posso executar a função quantas vezes quero e quero enfileirar as execuções extras da função. Você tem alguma ideia de como devo fazer isso? Eu tenho minha pergunta aqui . Você poderia dar uma olhada na minha pergunta? Qualquer ajuda seria ótimo!
Amir
3
Se você quiser vários parâmetros: download_thread = threading.Thread (target = function_that_downloads, args = (variable1, variable2, variableN))
georgeos
como passar vários args - como o método é add(a,b)e obter o valor de retorno desse método
Maifee Ul Asad
7

Normalmente, a maneira de fazer isso seria usar um pool de threads e downloads de fila que emitiria um sinal, também conhecido como um evento, quando o processamento da tarefa fosse concluído. Você pode fazer isso dentro do escopo do módulo de threading fornecido pelo Python.

Para realizar essas ações, eu usaria objetos de evento e o módulo Queue .

No entanto, uma demonstração rápida e suja do que você pode fazer usando uma threading.Threadimplementação simples pode ser vista abaixo:

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

Provavelmente faria sentido não pesquisar como estou fazendo acima. Nesse caso, eu mudaria o código para este:

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

Observe que não há um sinalizador daemon definido aqui.

Mahmoud Abdelkader
fonte
4

Eu prefiro usar gevent para este tipo de coisa:

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

Tudo é executado em um thread, mas sempre que uma operação do kernel é bloqueada, o gevent muda os contextos quando há outros "greenlets" em execução. As preocupações com o bloqueio, etc. são muito reduzidas, pois há apenas uma coisa em execução por vez, mas a imagem continuará a ser baixada sempre que uma operação de bloqueio for executada no contexto "principal".

Dependendo de quanto e de que tipo de coisa você deseja fazer em segundo plano, isso pode ser melhor ou pior do que soluções baseadas em encadeamento; certamente, é muito mais escalonável (ou seja, você pode fazer muito mais coisas em segundo plano), mas isso pode não ser motivo de preocupação na situação atual.

Shaunc
fonte
qual é o propósito desta linha from gevent import monkey; monkey.patch_all():?
nz_21 01 de
biblioteca padrão de patches para compatibilidade com gevent
gevent.org/api/gevent.monkey.html