Python异步回调与生成器
我正在尝试把一个同步的库转换成使用内部的异步输入输出框架。我有几个方法看起来像这样:
def foo:
....
sync_call_1() # synchronous blocking call
....
sync_call_2() # synchronous blocking call
....
return bar
对于每一个同步函数(sync_call_*
),我都写了一个对应的异步函数,这个异步函数需要一个回调函数。比如:
def async_call_1(callback=none):
# do the I/O
callback()
现在我有个问题,想问问python新手们——有什么简单的方法可以把现有的方法改成使用这些新的异步方法吗?也就是说,上面的foo()
方法现在需要变成:
def async_foo(callback):
# Do the foo() stuff using async_call_*
callback()
一个明显的选择是把一个回调函数传入每个异步方法,这样可以有效地“恢复”调用的“foo”函数,然后在方法的最后调用这个回调函数。不过,这样会让代码变得脆弱和难看,而且我每次调用async_call_*
方法时都需要添加一个新的回调函数。
有没有简单的方法可以使用python的习惯用法,比如生成器或协程?
3 个回答
你需要把函数 foo
也改成异步的。这样做怎么样呢?
@make_async
def foo(somearg, callback):
# This function is now async. Expect a callback argument.
...
# change
# x = sync_call1(somearg, some_other_arg)
# to the following:
x = yield async_call1, somearg, some_other_arg
...
# same transformation again
y = yield async_call2, x
...
# change
# return bar
# to a callback call
callback(bar)
而 make_async
可以这样定义:
def make_async(f):
"""Decorator to convert sync function to async
using the above mentioned transformations"""
def g(*a, **kw):
async_call(f(*a, **kw))
return g
def async_call(it, value=None):
# This function is the core of async transformation.
try:
# send the current value to the iterator and
# expect function to call and args to pass to it
x = it.send(value)
except StopIteration:
return
func = x[0]
args = list(x[1:])
# define callback and append it to args
# (assuming that callback is always the last argument)
callback = lambda new_value: async_call(it, new_value)
args.append(callback)
func(*args)
注意:我还没有测试过这个。
有几种方法可以同时处理多个任务。我们不能确定哪种方法最适合你,因为这需要更深入了解你的具体情况。可能最简单、最通用的方法就是使用线程。你可以看看这个问题,里面有一些相关的想法。
更新: 请谨慎看待这些内容,因为我对现代 Python 异步开发的了解不多,包括 gevent 和 asyncio,而且我实际上也没有太多异步代码的实际经验。
在 Python 中,有三种常见的无线程异步编程方法:
回调 - 虽然看起来不太好,但可以用,Twisted 在这方面做得不错。
生成器 - 这种方式不错,但要求你所有的代码都要遵循这种风格。
使用真正的任务单元的 Python 实现 - Stackless(已停止维护)和 greenlet。
不幸的是,理想情况下,整个程序应该使用一种风格,否则会变得复杂。如果你能接受你的库提供一个完全同步的接口,那你可能没问题,但如果你希望你的库中的多个调用能够并行工作,尤其是与其他异步代码并行工作,那么你需要一个可以与所有代码一起工作的公共事件“反应器”。
所以如果你有(或者希望用户有)其他异步代码在应用中,采用相同的模型可能是明智的选择。
如果你不想搞懂这一切的复杂性,可以考虑使用老旧的线程。虽然它们也不太好,但可以和其他所有东西一起工作。
如果你想了解协程如何帮助你,以及它们可能带来的复杂性,David Beazley 的《关于协程和并发的好奇课程》 是很不错的资料。
Greenlets 可能是最干净的方式,如果你能使用这个扩展。我没有使用过它们,所以不能多说。