我有一个需要一段时间才能运行的函数,需要在请求中运行它。处理此问题的最佳方法是什么,以便此请求在处理时不会阻塞主线程?我查看了@tornado.web.asynchronous
装饰器,但是当函数不是异步龙卷风模块时,这没什么用处。
class LongHandler(tornado.web.RequestHandler):
def get(self):
self.write(self.long_time_function())
def long_time_function(self):
time.sleep(5)
return "foo"
答案 0 :(得分:7)
如果你有一些阻塞任务对async事件循环不起作用,你必须把它放在一个单独的线程中。
如果您要进行无限数量的阻止任务,则需要使用线程池。
无论哪种方式,您都希望有一个包装器异步任务阻止来自线程任务的通知。
最简单的方法是使用预先构建的库,例如tornado-threadpool。*然后,您只需执行以下操作:
class LongHandler(tornado.web.RequestHandler):
@thread_pool.in_thread_pool
def long_time_function(self, callback):
time.sleep(5)
callback("foo")
如果你想自己做,this gist显示了你必须做的事情的一个例子 - 或者,当然,各种Tornado线程池库的源代码可以作为示例代码。
请记住Python GIL的局限性:如果你的后台任务是受CPU限制的(而且大部分工作是用Python完成的,而不是像Cump那样释放GIL就像numpy那样),你必须把它放在它在一个单独的过程中。快速搜索Tornado进程池库并没有找到很多不错的选项,但是在Python中调整线程池代码来处理池代码通常非常容易。**
*请注意,我并不是特别推荐该图书馆;这只是谷歌搜索中出现的第一件事,从快速浏览一下看起来很有用和正确。
**通常只需将concurrent.futures.ThreadPoolExecutor
替换为concurrent.futures.ProcessPoolExecutor
或将multiprocessing.dummy.Pool
替换为multiprocessing.Pool
即可。唯一的技巧是确保所有的任务参数和返回值都很小并且可以选择。
答案 1 :(得分:1)
使用其他答案中所述的其他线程是一种方法。如果您的函数可以拆分为片段(例如,内部有一个循环有很多迭代),您还可以使用IOLoop.add_callback()
将其拆分,以便通过处理其他请求来交织它。以下是如何执行此操作的示例:why my coroutine blocks whole tornado instance?