funkcja tła w Pythonie


88

Mam skrypt w Pythonie, który czasami wyświetla użytkownikowi obrazy. Obrazy mogą czasami być dość duże i często są ponownie wykorzystywane. Wyświetlanie ich nie jest krytyczne, ale wyświetlanie powiązanego z nimi komunikatu jest. Mam funkcję, która pobiera potrzebny obraz i zapisuje go lokalnie. W tej chwili jest uruchamiany w tekście z kodem, który wyświetla komunikat dla użytkownika, ale czasami może to zająć ponad 10 sekund w przypadku obrazów nielokalnych. Czy istnieje sposób, aby wywołać tę funkcję, gdy jest potrzebna, ale uruchomić ją w tle, podczas gdy kod nadal jest wykonywany? Po prostu użyłbym domyślnego obrazu, dopóki właściwy nie stanie się dostępny.

Odpowiedzi:


129

Zrób coś takiego:

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

następnie w tekście zrób coś takiego:

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

Możesz sprawdzić, czy wątek się skończył, zanim przejdziesz do innych rzeczy, dzwoniąc download_thread.isAlive()


Tłumacz pozostaje otwarty do zamknięcia nici. (przykład import threading, time; wait=lambda: time.sleep(2); t=threading.Thread(target=wait); t.start(); print('end')). Miałem nadzieję, że „tło” sugerowało również brak relacji.
ThorSummoner

3
Wątki @ThorSummoner są zawarte w tym samym procesie. Jeśli chcesz odrodzić nowy proces, zamiast tego zajrzyj do modułów subprocesslub multiprocessingpython.
TorelTwiddler

@TorelTwiddler Chcę uruchomić funkcję w tle, ale mam pewne ograniczenia zasobów i nie mogę uruchamiać funkcji tyle razy, ile chcę i chcę ustawić w kolejce dodatkowe wykonania funkcji. Czy masz pomysł, jak mam to zrobić? Mam pytanie tutaj . Czy mógłbyś spojrzeć na moje pytanie? Każda pomoc byłaby świetna!
Amir

3
Jeśli chcesz mieć wiele parametrów: download_thread = threading.Thread (target = function_that_downloads, args = (
zmienna1, zmienna2, zmiennaN

jak przekazać wiele argumentów - tak jak metoda add(a,b)i uzyskać wartość
zwracaną przez

7

Zwykle sposobem na to byłoby użycie puli wątków i pobrań kolejki, które spowodowałyby wysłanie sygnału, czyli zdarzenia, po zakończeniu przetwarzania tego zadania. Możesz to zrobić w ramach modułu obsługi wątków, który zapewnia Python.

Aby wykonać te czynności, użyłbym obiektów zdarzeń i modułu Queue .

threading.ThreadPoniżej można jednak zobaczyć szybką i brudną demonstrację tego, co można zrobić za pomocą prostej implementacji:

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

Prawdopodobnie rozsądnie byłoby nie sondować tak, jak robię powyżej. W takim przypadku zmieniłbym kod na ten:

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

Zauważ, że nie ma tu ustawionej flagi demona.


4

Wolę używać gevent do tego typu rzeczy:

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

Wszystko działa w jednym wątku, ale za każdym razem, gdy blokuje się operacja jądra, gevent zmienia konteksty, gdy są uruchomione inne „zielone środowiska”. Obawy o blokowanie itp. Są znacznie ograniczone, ponieważ w danym momencie działa tylko jedna rzecz, ale obraz będzie nadal pobierany, gdy operacja blokowania zostanie wykonana w kontekście „głównym”.

W zależności od tego, ile i jakiego rodzaju rzeczy chcesz robić w tle, może to być lepsze lub gorsze niż rozwiązania oparte na wątkach; z pewnością jest znacznie bardziej skalowalny (tj. możesz zrobić o wiele więcej rzeczy w tle), ale w obecnej sytuacji może to nie stanowić problemu.


jaki jest cel tej linii from gevent import monkey; monkey.patch_all():?
nz_21

łata standardową bibliotekę pod kątem zgodności z gevent
gevent.org/api/gevent.monkey.html
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.