【问题标题】:How can I run a coroutine but not wait for it?如何运行协程但不等待它?
【发布时间】:2019-08-26 22:13:21
【问题描述】:

我有一个如下所示的非异步函数:

def do_stuff(on_finished):
    result = # complicated calculations here
    on_finished(result)

我传入的回调或多或少是这样的:

async def on_finished(self, result):
    response = await post_over_http(result)
    self.last_status = response.status 

当我打电话给do_stuff 时,我想要发生的事情是这样的:

  1. do_stuff 执行并调用 on_finished
  2. on_finished 执行,通过 HTTP 发布结果,然后立即返回。
  3. do_stuff 现在立即返回。
  4. 稍后,HTTP 响应返回,执行返回到on_finished 的第二行。

重要的是,我不希望 do_stuff 是异步的。出于架构原因,我希望 do_stuff 与网络代码的异步特性隔离,所以我不想仅仅因为使用它的某些代码是异步的而使其异步。

在 JavaScript 中这没有问题 - 基本上将上面的代码直接转录为 JavaScript,我将获得所需的行为。 onFinished 将返回一个 Promise,doStuff 不会等待并立即返回,但是当 Promise 稍后解析时,onFinished 的第二行运行。这在 Python 中可能吗?我不确定如何实现它。使用上面的代码,我想我只是在do_stuff 的最后一行创建了一个协程,但从不调用它。

【问题讨论】:

  • 协程需要一个事件循环。如果您需要它作为“与异步性质隔离” - 尝试另一种并行方法
  • 鉴于你的拱门。约束,在 main 中集成事件循环的创建和启动可能并不容易

标签: python python-asyncio


【解决方案1】:

您可以像这样设计do_stuff 函数:

def do_stuff(on_finished):
    async def _do_complicated_calculation():
        result = # do the calculation here & the post request
        await on_finished(result)
    asyncio.ensure_future(_do_complicated_calculation())
    return "ok"

当您调用do_stuff(...) 时,复杂的计算将被添加到 asyncio 事件循环中,因此它会异步执行。如果你不打算在主线程中启动它,你应该让事件循环在不同的线程中运行。

由于_do_complicated_calculation() 是异步的,do_stuff 将首先返回"ok",在您的计算完成后,将调用on_finished(...)

【讨论】:

  • 我喜欢。但现在展示事件循环的创建如何与
【解决方案2】:

如果我理解 JavaScript 的类比,你想要的是这样的:

def do_stuff(on_finished):
    result = ...
    asyncio.create_task(on_finished(result))

最后一行生成了一个任务,它处理结果而不实际等待它完成。这就是你在 JavaScript 中通过简单地创建一个 Promise 就能得到的,而在 Python 中你必须更明确一些。

当然,do_stuff 必须在事件循环中运行,并且计算不能阻塞(或花费太长时间才能完成),但在 JavaScript 中也是如此。

【讨论】:

  • 也许我误解了什么。这对我不起作用 - 这是minimal example
  • @JackM 这只是因为main()(以及整个程序)在on_finished 有机会被调用之前退出。添加例如await asyncio.sleep(1)main() 的末尾,它会起作用。
  • 好吧,这行得通,但是在updated example 中你可以看到还有另一个问题——do_stuff 在执行on_finished 之前返回。在此代码的 JS 等效项中,on_finished 将立即执行,直到第一个 await,然后才暂停,然后返回到do_stuff
  • @JackM 你可以这样做,但它并不漂亮——你可以让on_finished 成为一个普通的def,它首先执行非异步操作并返回一个实际等待的协程——like this .
【解决方案3】:

让同步函数返回其结果并使用run_in_executor 从异步代码调用将在其自己的线程上运行的同步代码,而不是传递回调。

def do_stuff():
    result = # complicated calculations here
    return result

async def main():
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(None, do_stuff)
    response = await post_over_http(result)
    self.last_status = response.status 

未经测试。

【讨论】:

    【解决方案4】:

    这就是 Javascript 的行为与 Python 不同的原因。

    ECMAScript 2015 引入了 Job Queue 的概念,使用 通过 Promises(也在 ES6/ES2015 中引入)。这是一种执行方式 尽快得到异步函数的结果,而不是被放入 在调用堆栈的末尾。

    在当前函数结束之前解决的承诺将是 在当前函数之后立即执行。

    https://nodejs.dev/the-nodejs-event-loop

    【讨论】:

      猜你喜欢
      • 2017-02-24
      • 2020-11-02
      • 1970-01-01
      • 1970-01-01
      • 2018-09-19
      • 1970-01-01
      • 1970-01-01
      • 2020-03-11
      • 1970-01-01
      相关资源
      最近更新 更多