Python中的后台函数

114 投票
4 回答
138770 浏览
提问于 2025-04-17 00:14

我有一个Python脚本,有时候会给用户显示图片。这些图片有时候比较大,而且经常会被重复使用。显示这些图片并不是特别重要,但与它们相关的信息却很重要。我有一个函数可以下载需要的图片并保存在本地。现在这个函数和显示信息的代码是一起运行的,但对于非本地的图片,有时候需要超过10秒才能显示。有没有办法让我在需要的时候调用这个函数,但让它在后台运行,同时代码可以继续执行呢?我可以先用一张默认的图片,等到正确的图片下载完成后再显示。

4 个回答

6

我更喜欢用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会在其他“绿色小任务”运行时切换上下文。这样就减少了对锁等问题的担忧,因为同一时间只有一件事情在运行,而当“主”上下文中有阻塞操作时,图片仍然会继续下载。

根据你想在后台做多少事情以及做什么事情,这种方法可能比基于线程的解决方案要好,也可能更差;当然,它在扩展性上要好得多(也就是说,你可以在后台同时做很多事情),但在当前情况下,这可能不是一个重要的问题。

7

通常,处理这种情况的方法是使用线程池,并将下载任务排队。当一个任务处理完成时,它会发出一个信号,也就是一个事件。你可以在Python提供的线程模块中做到这一点。

为了执行这些操作,我会使用事件对象队列模块

不过,下面是一个简单的示范,展示了如何使用简单的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

注意,这里没有设置守护线程标志。

175

可以这样做:

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() 来查看。

撰写回答