фоновая функция в Python

88

У меня есть сценарий Python, который иногда отображает изображения пользователю. Иногда изображения могут быть довольно большими, и они часто используются повторно. Их отображение не критично, но отображение связанного с ними сообщения критично. У меня есть функция, которая загружает необходимое изображение и сохраняет его локально. Прямо сейчас он запускается вместе с кодом, который отображает сообщение пользователю, но иногда это может занять более 10 секунд для нелокальных изображений. Есть ли способ вызвать эту функцию, когда она понадобится, но запустить ее в фоновом режиме, пока код продолжает выполняться? Я бы просто использовал изображение по умолчанию, пока не стал доступен правильный.

Дэн Хлавенка
источник

Ответы:

129

Сделайте что-нибудь вроде этого:

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

затем вставьте, сделайте что-то вроде этого:

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

Вы можете проверить, завершился ли поток, прежде чем переходить к другим вещам, вызвав download_thread.isAlive()

TorelTwiddler
источник
Интерпретатор остается открытым, пока поток не закроется. (пример import threading, time; wait=lambda: time.sleep(2); t=threading.Thread(target=wait); t.start(); print('end')). Я надеялся, что "фон" тоже будет отстраненным.
ThorSummoner
3
@ThorSummoner Все потоки содержатся в одном процессе. Если вы хотите создать новый процесс, вместо этого вы захотите изучить модули subprocessили multiprocessingpython.
TorelTwiddler
@TorelTwiddler Я хочу запустить функцию в фоновом режиме, но у меня есть некоторые ограничения ресурсов, и я не могу запускать функцию столько раз, сколько хочу, и хочу поставить в очередь дополнительные выполнения функции. Ты хоть представляешь, как мне это сделать? У меня есть вопрос здесь . Не могли бы вы взглянуть на мой вопрос? Любая помощь была бы замечательной!
Amir
3
Если вам нужно несколько параметров: download_thread = threading.Thread (target = function_that_downloads, args = (variable1, variable2, variableN))
georgeos 06
как передать несколько аргументов - например, метод add(a,b)и получить возвращаемое значение этого метода
Maifee Ul Asad
7

Обычно способ сделать это - использовать пул потоков и загрузку очереди, которые будут выдавать сигнал, иначе говоря, событие, когда эта задача завершает обработку. Вы можете сделать это в рамках модуля потоковой передачи, который предоставляет Python.

Для выполнения указанных действий я бы использовал объекты событий и модуль Queue .

Однако быстрая и грязная демонстрация того, что вы можете сделать с помощью простой threading.Threadреализации, можно увидеть ниже:

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

Вероятно, имеет смысл не проводить опрос, как я делаю выше. В этом случае я бы изменил код на этот:

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

Обратите внимание, что здесь не установлен флаг демона.

Махмуд Абделькадер
источник
4

Я предпочитаю использовать gevent для таких вещей:

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

Все выполняется в одном потоке, но всякий раз, когда операция ядра блокируется, gevent переключает контексты, когда выполняются другие «гринлеты». Беспокойство о блокировке и т. Д. Значительно сокращается, так как одновременно выполняется только одна вещь, но изображение будет продолжать загружаться всякий раз, когда операция блокировки выполняется в «основном» контексте.

В зависимости от того, сколько и что вы хотите делать в фоновом режиме, это может быть лучше или хуже, чем решения на основе потоков; конечно, он гораздо более масштабируемый (то есть вы можете делать гораздо больше в фоновом режиме), но в текущей ситуации это может не вызывать беспокойства.

Shaunc
источник
какова цель этой строки from gevent import monkey; monkey.patch_all():?
nz_21
исправляет стандартную библиотеку для совместимости с gevent
gevent.org/api/gevent.monkey.html