Python 中的后台函数

我有一个 Python 脚本,它有时会向用户显示图像。这些图像有时可能非常大,并且经常被重用。显示它们不是关键,但显示与它们相关的消息是关键。我有一个功能,下载需要的图像,并保存在本地。现在,它是使用向用户显示消息的代码内联运行的,但是对于非本地图像,这有时需要超过10秒钟。有没有一种方法可以在需要时调用这个函数,但在代码继续执行时在后台运行它?我只会使用一个默认的图像,直到正确的一个变得可用。

121660 次浏览

Typically the way to do this would be to use a thread pool and queue downloads which would issue a signal, a.k.a an event, when that task has finished processing. You can do this within the scope of the threading module Python provides.

To perform said actions, I would use event objects and the Queue module.

However, a quick and dirty demonstration of what you can do using a simple threading.Thread implementation can be seen below:

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

It would probably make sense to not poll like I'm doing above. In which case, I would change the code to this:

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

Notice that there's no daemon flag set here.

Do something like this:

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

then inline, do something like this:

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

You may want to check if the thread has finished before going on to other things by calling download_thread.isAlive()

I prefer to use gevent for this sort of thing:

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

Everything runs in one thread, but whenever a kernel operation blocks, gevent switches contexts when there are other "greenlets" running. Worries about locking, etc are much reduced, as there is only one thing running at a time, yet the image will continue to download whenever a blocking operation executes in the "main" context.

Depending on how much, and what kind of thing you want to do in the background, this can be either better or worse than threading-based solutions; certainly, it is much more scaleable (ie you can do many more things in the background), but that might not be of concern in the current situation.

simple way.


import threading
import os


def killme():
if keyboard.read_key() == "q":
print("Bye ..........")
os._exit(0)


threading.Thread(target=killme, name="killer").start()


if you want to add more keys add defs and threading.Thread(target=killme, name="killer").start() lines more

Looking bad but works 1000% then complex codes