想象一下,我们希望在Tornado应用程序上下文中异步执行一个处理繁重计算任务的函数。此外,我们想通过将其结果存储到磁盘来懒惰地评估函数,而不是为相同的参数重新运行该函数两次。
如果没有缓存结果(memoization),可以执行以下操作:
def complex_computation(arguments):
...
return result
@gen.coroutine
def complex_computation_caller(arguments):
...
result = complex_computation(arguments)
raise gen.Return(result)
假设要实现功能记忆,我们从 joblib 中选择记忆类。通过简单地用@mem.cache
修饰函数,可以很容易地记住函数:
@mem.cache
def complex_computation(arguments):
...
return result
其中mem
可以是mem = Memory(cachedir=get_cache_dir())
。
现在考虑将两者结合起来,我们在执行器上执行计算复杂的函数:
class TaskRunner(object):
def __init__(self, loop=None, number_of_workers=1):
self.executor = futures.ThreadPoolExecutor(number_of_workers)
self.loop = loop or IOLoop.instance()
@run_on_executor
def run(self, func, *args, **kwargs):
return func(*args, **kwargs)
mem = Memory(cachedir=get_cache_dir())
_runner = TaskRunner(1)
@mem.cache
def complex_computation(arguments):
...
return result
@gen.coroutine
def complex_computation_caller(arguments):
result = yield _runner.run(complex_computation, arguments)
...
raise gen.Return(result)
所以第一个问题是上述方法在技术上是否正确?
现在让我们考虑以下情况:
@gen.coroutine
def first_coroutine(arguments):
...
result = yield second_coroutine(arguments)
raise gen.Return(result)
@gen.coroutine
def second_coroutine(arguments):
...
result = yield third_coroutine(arguments)
raise gen.Return(result)
第二个问题是如何记住second_coroutine
?做以下事情是否正确:
@gen.coroutine
def first_coroutine(arguments):
...
mem = Memory(cachedir=get_cache_dir())
mem_second_coroutine = mem(second_coroutine)
result = yield mem_second_coroutine(arguments)
raise gen.Return(result)
@gen.coroutine
def second_coroutine(arguments):
...
result = yield third_coroutine(arguments)
raise gen.Return(result)
[更新I] Caching and reusing a function result in Tornado讨论了使用functools.lru_cache
或repoze.lru.lru_cache
作为第二个问题的解决方案。
答案 0 :(得分:1)
Tornado协同程序返回的Future
对象是可重用的,因此它通常可以使用内存缓存,例如functools.lru_cache
,如this question中所述。请务必将缓存装饰器放在@gen.coroutine
之前。
磁盘缓存(cachedir
的{{1}}参数似乎暗示)是比较棘手的,因为Memory
对象通常不能写入磁盘。您的Future
示例应该有效,但它与其他人的做法根本不同,因为TaskRunner
不是协程。您的上一个示例无效,因为它试图将complex_calculation
对象放入缓存中。
相反,如果你想用装饰器缓存东西,你需要一个用第二个协程包装内部协程的装饰器。像这样:
Future