Python异步回调与生成器

10 投票
3 回答
11555 浏览
提问于 2025-04-15 16:26

我正在尝试把一个同步的库转换成使用内部的异步输入输出框架。我有几个方法看起来像这样:

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 个回答

2

你需要把函数 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)

注意:我还没有测试过这个。

2

有几种方法可以同时处理多个任务。我们不能确定哪种方法最适合你,因为这需要更深入了解你的具体情况。可能最简单、最通用的方法就是使用线程。你可以看看这个问题,里面有一些相关的想法。

10

更新: 请谨慎看待这些内容,因为我对现代 Python 异步开发的了解不多,包括 geventasyncio,而且我实际上也没有太多异步代码的实际经验。


在 Python 中,有三种常见的无线程异步编程方法:

  1. 回调 - 虽然看起来不太好,但可以用,Twisted 在这方面做得不错。

  2. 生成器 - 这种方式不错,但要求你所有的代码都要遵循这种风格。

  3. 使用真正的任务单元的 Python 实现 - Stackless(已停止维护)和 greenlet

不幸的是,理想情况下,整个程序应该使用一种风格,否则会变得复杂。如果你能接受你的库提供一个完全同步的接口,那你可能没问题,但如果你希望你的库中的多个调用能够并行工作,尤其是与其他异步代码并行工作,那么你需要一个可以与所有代码一起工作的公共事件“反应器”。

所以如果你有(或者希望用户有)其他异步代码在应用中,采用相同的模型可能是明智的选择。

如果你不想搞懂这一切的复杂性,可以考虑使用老旧的线程。虽然它们也不太好,但可以和其他所有东西一起工作。

如果你想了解协程如何帮助你,以及它们可能带来的复杂性,David Beazley 的《关于协程和并发的好奇课程》 是很不错的资料。

Greenlets 可能是最干净的方式,如果你能使用这个扩展。我没有使用过它们,所以不能多说。

撰写回答