如何用等效的异步表达式替换 `yield gen.Task(fn, argument)`?
How to replace `yield gen.Task(fn, argument)` with an equivalent asyncio expression?
问题:
我正在尝试更新一些旧代码(我没有写),它使用了过时版本的 Tornado 和 gen.Task
,以使用当前版本的 Tornado 和 asyncio。这主要是简单的,除了这个表达式(1)我不完全理解,以及(2)我不知道如何用等效的 asyncio 表达式替换。
我要替换的单行代码的形式是:
response = yield gen.Task(fn, request)
其中函数fn
的签名是fn(request, callback)
,然后在后面的代码中(也就是gen.coroutine
的方法定义)我们运行callback(response)
。而且我认为 fn
本身可能是异步的,尽管我不确定,并且不明白如果它是真的会产生什么影响。
编辑: 根据另一个答案的建议,我能够将其重写为
fn(request=request, callback=(yield gen.Callback("key")))
response = yield gen.Wait("key")
当然,尽管 Tornado 6 的发行说明说 gen.Wait
和 gen.Callback
都已删除。文档的早期版本说 gen.Wait
已弃用,应由 tornado.concurrent.Futures
替换,不幸的是它没有指定 如何 这样做,特别是考虑到如何gen.Wait
需要一个 key
参数,而 concurrent.futures.Futures
(显然是 asyncio.Future
的别名)明确地具有 no 支持 key
参数。所以我不明白这是可以以某种方式替换的说法。
此外 add_done_callback
似乎不足以满足此目的,因为文档明确指出回调只能接受一个参数,但 fn
有两个。
虽然到目前为止效果最好的(并且可能真的有效,前提是我可以在其他地方正确地将 gen.coroutine
转换为 async def
)似乎是:
response = await asyncio.Future().add_done_callback(partial(fn, request=request))
这只会产生意想不到的行为(无休止的阻塞,貌似可能是因为上面提到的 gen.coroutine
到 async def
的转换不足)并且 没有错误. 这给出了错误 TypeError: Can't await NoneType
。所以我一点头绪都没有。
背景: 我试图找出 Tornado 在 gen.Task
更新并最终删除时给出的建议。然而,在版本 6 的变更日志中,它没有说明如何使用 gen.Task
更新我们的代码,只是说它已被删除。我发现 at least one question on Whosebug as well as a Tornado GitHub issue 说(没有给出具体示例或实现细节)gen.Task
的任何实例都可以用 gen.coroutine
替换。然而,由于我不太了解异步编程的一般概念,也不了解 tornado.gen.Task
的细节,所以我很难弄清楚如何做到这一点。不过这会很棒,因为用 asyncio 等价物替换 gen.coroutine
似乎很容易——只需 async def
和 await
一切。
根据文档,yield gen.Task
的结果应该是:
Takes a function (and optional additional arguments) and runs it with those arguments plus a callback keyword argument. The argument passed to the callback is returned as the result of the yield expression.
Changed in version 4.0: gen.Task
is now a function that returns a Future
...
然而,这似乎比可以用 gen.coroutine
替换的东西更复杂,因为它直接创建了一个 Future
,而不是 await
ing 异步函数的结果,并且在 asyncio 中创建和使用 futures 的方法有很多种,我依稀记得在某处读到 Tornado futures 和 asyncio futures 实际上并不等同。
这涉及到异步和函数式编程,这使得问题更加难以理解——我模糊地掌握了函数式部分,但我对异步编程的理解很差,以至于它突然也使功能方面现在也很难理解。
到目前为止我尝试了什么:
response = yield asyncio.add_done_callback(functools.partial(fn, request=request))
给出错误 AttributeError: module 'asyncio' has no attribute 'add_done_callback'
,很好,我知道 add_done_callback
应该是 asyncio.Future
对象的属性,但是我 make/choose 成为 asyncio.Future
?
response = yield asyncio.Task(partial(fn, request=request).func)
给出了错误 TypeError: a coroutine was expected, got <bound method Class.fn of <SubClass object at 0x7f5df254b748>>
。
我尝试使用部分对象的 .func
属性的原因是因为当我尝试时:
response = yield asyncio.Task(partial(fn, request=request))
我收到错误 TypeError: a coroutine was expected, got functools.partial(<bound method Class.fn of <SubClass object at 0x7ffaad59b710>>, request=<tornado.httpclient._RequestProxy object at 0x7ffaad4c8080>)
。但我只是试图这样做,因为更直接的解决方案尝试导致对参数数量不正确的抱怨。
特别是尝试最幼稚的事情之一,
response = yield asyncio.Task(fn, request)
导致了事后可预测的错误TypeError: Task() takes at most 1 positional arguments (2 given)
。 The release notes for Tornado 5.0 说在内部所有 gen.Task
都被替换为 asyncio.Task
,但这让我很难理解如何,因为看起来 asyncio.Task
是不充分的自己处理回调。
本来比较乐观,希望asyncio.Task
注意到fn
的调用签名是fn(request, callback)
,然后理解fn(request)
是部分应用函数。但是当然
response = yield asyncio.Task(fn(request))
给出错误 TypeError: fn() missing 1 required positional argument: 'callback'
。
更令人困惑的是 fn
本身可能是异步的,所以我认为使用 asyncio
我可能只能部分应用它并取回一个接受回调的异步函数作为一个选项
response = yield fn(request)
但这导致了错误 TypeError: fn() missing 1 required positional argument: 'callback'
。
我还尝试使用推荐的 ensure_future
和 create_task
函数在 asyncio 中创建任务或未来(我不确定我需要创建哪两个),因为使用 [=根据 asyncio 文档,强烈建议不要直接使用 77=]。结果不太好:
response = yield asyncio.create_task(fn, request)
给出错误 TypeError: create_task() takes 1 positional argument but 2 were given
。
使用 ensure_future
没有得到更好的结果:
response = asyncio.ensure_future(functools.partial(fn, request))
给出结果TypeError: An asyncio.Future, a coroutine or an awaitable is required
,并没有使用partial
response = asyncio.ensure_future(super().fetch_impl, request=request)
给出错误 TypeError: ensure_future() got an unexpected keyword argument 'request'
。
如果相关,fn
是 Tornado 的 CurlAsyncHTTPClient
的 fetch_impl
方法。
类似问题: 这两个问题看起来很相似,但我不明白如何将他们的答案用于我的问题。它们可能是适用的,但我对一般的异步编程,尤其是 asyncio 的理解非常糟糕,而且我非常愚蠢。因此,如果能像我五岁一样解释其他两个问题的答案,我们也将不胜感激。如果您能为我的愚蠢+无知鼓起耐心,我们将不胜感激。
How does 'yield' work in tornado when making an asynchronous call?
Extending tornado.gen.Task
I have found at least one question on Whosebug as well as a Tornado GitHub issue where it is said (without giving specific examples or implementation details) that any instance of gen.Task can be replaced with a gen.coroutine. However, since I do not understand the general concepts of asynchronous programming very well, nor the particulars of tornado.gen.Task, it is very difficult for me to figure out how this could be done. It would be great though since it seems to be easy to replace gen.coroutine's with asyncio equivalents -- just async def and await everything.
您正在关注 "how do I call this thing that takes a callback"。问题是回调的整个概念已被弃用并从 Tornado 中删除,因此不再有优雅的方式来调用需要回调的东西。预期的前进道路是改变需要回调的东西(即fn
)使用gen.coroutine
and/or return Future
,然后你可以直接从你的其他协程中调用它。
如果 fn
使用 @gen.engine
(Tornado 协程的第一个版本),这相当简单:只需将 @gen.engine
替换为 @gen.coroutine
并删除任何引用callback
参数。该函数可能以 callback(response)
结尾;将其替换为 raise gen.Return(response)
。
如果 fn
只是在没有 @gen.engine
的情况下使用原始回调,那么将其更新为以现代方式工作将更加困难,这是需要在 [=46= 上处理的事情] 基础所以我不能在这里给你有用的指导。
如果您遇到需要回调的内容并且无法更改它,则此序列 几乎 等同于 response = yield gen.Task(fn, request)
:
future = tornado.concurrent.Future()
fn(request, callback=future.set_result)
response = yield future
这与 gen.Task
之间的区别与错误处理有关。如果 fn
引发异常,gen.Task
有一些昂贵的魔法来确保它可以捕获该异常并 re-raise 在调用函数中捕获它。即使对于不使用 gen.Task
的应用程序,保持这种魔力也会产生一些性能成本,这就是它最终被弃用和删除的原因(以及与回调相关的所有内容)。因此,您可能需要更改 fn
以确保捕获并适当报告任何可能的异常(同样,推荐的方法是转移到协程,在协程中异常处理的工作方式更符合您的预期)。
如果您可以将函数更新为 async def
(因此使用 await
),那么您需要的可以表示为:
future = asyncio.get_event_loop().create_future()
fn(request=request, callback=future.set_result)
response = await future
可以等待"future"对象,其set_result
方法恢复被等待者。 fn
不需要知道未来,但它只看到一个回调函数。
问题:
我正在尝试更新一些旧代码(我没有写),它使用了过时版本的 Tornado 和 gen.Task
,以使用当前版本的 Tornado 和 asyncio。这主要是简单的,除了这个表达式(1)我不完全理解,以及(2)我不知道如何用等效的 asyncio 表达式替换。
我要替换的单行代码的形式是:
response = yield gen.Task(fn, request)
其中函数fn
的签名是fn(request, callback)
,然后在后面的代码中(也就是gen.coroutine
的方法定义)我们运行callback(response)
。而且我认为 fn
本身可能是异步的,尽管我不确定,并且不明白如果它是真的会产生什么影响。
编辑: 根据另一个答案的建议,我能够将其重写为
fn(request=request, callback=(yield gen.Callback("key")))
response = yield gen.Wait("key")
当然,尽管 Tornado 6 的发行说明说 gen.Wait
和 gen.Callback
都已删除。文档的早期版本说 gen.Wait
已弃用,应由 tornado.concurrent.Futures
替换,不幸的是它没有指定 如何 这样做,特别是考虑到如何gen.Wait
需要一个 key
参数,而 concurrent.futures.Futures
(显然是 asyncio.Future
的别名)明确地具有 no 支持 key
参数。所以我不明白这是可以以某种方式替换的说法。
此外 add_done_callback
似乎不足以满足此目的,因为文档明确指出回调只能接受一个参数,但 fn
有两个。
虽然到目前为止效果最好的(并且可能真的有效,前提是我可以在其他地方正确地将 gen.coroutine
转换为 async def
)似乎是:
response = await asyncio.Future().add_done_callback(partial(fn, request=request))
这只会产生意想不到的行为(无休止的阻塞,貌似可能是因为上面提到的 这给出了错误 gen.coroutine
到 async def
的转换不足)并且 没有错误.TypeError: Can't await NoneType
。所以我一点头绪都没有。
背景: 我试图找出 Tornado 在 gen.Task
更新并最终删除时给出的建议。然而,在版本 6 的变更日志中,它没有说明如何使用 gen.Task
更新我们的代码,只是说它已被删除。我发现 at least one question on Whosebug as well as a Tornado GitHub issue 说(没有给出具体示例或实现细节)gen.Task
的任何实例都可以用 gen.coroutine
替换。然而,由于我不太了解异步编程的一般概念,也不了解 tornado.gen.Task
的细节,所以我很难弄清楚如何做到这一点。不过这会很棒,因为用 asyncio 等价物替换 gen.coroutine
似乎很容易——只需 async def
和 await
一切。
根据文档,yield gen.Task
的结果应该是:
Takes a function (and optional additional arguments) and runs it with those arguments plus a callback keyword argument. The argument passed to the callback is returned as the result of the yield expression.
Changed in version 4.0:
gen.Task
is now a function that returns aFuture
...
然而,这似乎比可以用 gen.coroutine
替换的东西更复杂,因为它直接创建了一个 Future
,而不是 await
ing 异步函数的结果,并且在 asyncio 中创建和使用 futures 的方法有很多种,我依稀记得在某处读到 Tornado futures 和 asyncio futures 实际上并不等同。
这涉及到异步和函数式编程,这使得问题更加难以理解——我模糊地掌握了函数式部分,但我对异步编程的理解很差,以至于它突然也使功能方面现在也很难理解。
到目前为止我尝试了什么:
response = yield asyncio.add_done_callback(functools.partial(fn, request=request))
给出错误 AttributeError: module 'asyncio' has no attribute 'add_done_callback'
,很好,我知道 add_done_callback
应该是 asyncio.Future
对象的属性,但是我 make/choose 成为 asyncio.Future
?
response = yield asyncio.Task(partial(fn, request=request).func)
给出了错误 TypeError: a coroutine was expected, got <bound method Class.fn of <SubClass object at 0x7f5df254b748>>
。
我尝试使用部分对象的 .func
属性的原因是因为当我尝试时:
response = yield asyncio.Task(partial(fn, request=request))
我收到错误 TypeError: a coroutine was expected, got functools.partial(<bound method Class.fn of <SubClass object at 0x7ffaad59b710>>, request=<tornado.httpclient._RequestProxy object at 0x7ffaad4c8080>)
。但我只是试图这样做,因为更直接的解决方案尝试导致对参数数量不正确的抱怨。
特别是尝试最幼稚的事情之一,
response = yield asyncio.Task(fn, request)
导致了事后可预测的错误TypeError: Task() takes at most 1 positional arguments (2 given)
。 The release notes for Tornado 5.0 说在内部所有 gen.Task
都被替换为 asyncio.Task
,但这让我很难理解如何,因为看起来 asyncio.Task
是不充分的自己处理回调。
本来比较乐观,希望asyncio.Task
注意到fn
的调用签名是fn(request, callback)
,然后理解fn(request)
是部分应用函数。但是当然
response = yield asyncio.Task(fn(request))
给出错误 TypeError: fn() missing 1 required positional argument: 'callback'
。
更令人困惑的是 fn
本身可能是异步的,所以我认为使用 asyncio
我可能只能部分应用它并取回一个接受回调的异步函数作为一个选项
response = yield fn(request)
但这导致了错误 TypeError: fn() missing 1 required positional argument: 'callback'
。
我还尝试使用推荐的 ensure_future
和 create_task
函数在 asyncio 中创建任务或未来(我不确定我需要创建哪两个),因为使用 [=根据 asyncio 文档,强烈建议不要直接使用 77=]。结果不太好:
response = yield asyncio.create_task(fn, request)
给出错误 TypeError: create_task() takes 1 positional argument but 2 were given
。
使用 ensure_future
没有得到更好的结果:
response = asyncio.ensure_future(functools.partial(fn, request))
给出结果TypeError: An asyncio.Future, a coroutine or an awaitable is required
,并没有使用partial
response = asyncio.ensure_future(super().fetch_impl, request=request)
给出错误 TypeError: ensure_future() got an unexpected keyword argument 'request'
。
如果相关,fn
是 Tornado 的 CurlAsyncHTTPClient
的 fetch_impl
方法。
类似问题: 这两个问题看起来很相似,但我不明白如何将他们的答案用于我的问题。它们可能是适用的,但我对一般的异步编程,尤其是 asyncio 的理解非常糟糕,而且我非常愚蠢。因此,如果能像我五岁一样解释其他两个问题的答案,我们也将不胜感激。如果您能为我的愚蠢+无知鼓起耐心,我们将不胜感激。
How does 'yield' work in tornado when making an asynchronous call?
Extending tornado.gen.Task
I have found at least one question on Whosebug as well as a Tornado GitHub issue where it is said (without giving specific examples or implementation details) that any instance of gen.Task can be replaced with a gen.coroutine. However, since I do not understand the general concepts of asynchronous programming very well, nor the particulars of tornado.gen.Task, it is very difficult for me to figure out how this could be done. It would be great though since it seems to be easy to replace gen.coroutine's with asyncio equivalents -- just async def and await everything.
您正在关注 "how do I call this thing that takes a callback"。问题是回调的整个概念已被弃用并从 Tornado 中删除,因此不再有优雅的方式来调用需要回调的东西。预期的前进道路是改变需要回调的东西(即fn
)使用gen.coroutine
and/or return Future
,然后你可以直接从你的其他协程中调用它。
如果 fn
使用 @gen.engine
(Tornado 协程的第一个版本),这相当简单:只需将 @gen.engine
替换为 @gen.coroutine
并删除任何引用callback
参数。该函数可能以 callback(response)
结尾;将其替换为 raise gen.Return(response)
。
如果 fn
只是在没有 @gen.engine
的情况下使用原始回调,那么将其更新为以现代方式工作将更加困难,这是需要在 [=46= 上处理的事情] 基础所以我不能在这里给你有用的指导。
如果您遇到需要回调的内容并且无法更改它,则此序列 几乎 等同于 response = yield gen.Task(fn, request)
:
future = tornado.concurrent.Future()
fn(request, callback=future.set_result)
response = yield future
这与 gen.Task
之间的区别与错误处理有关。如果 fn
引发异常,gen.Task
有一些昂贵的魔法来确保它可以捕获该异常并 re-raise 在调用函数中捕获它。即使对于不使用 gen.Task
的应用程序,保持这种魔力也会产生一些性能成本,这就是它最终被弃用和删除的原因(以及与回调相关的所有内容)。因此,您可能需要更改 fn
以确保捕获并适当报告任何可能的异常(同样,推荐的方法是转移到协程,在协程中异常处理的工作方式更符合您的预期)。
如果您可以将函数更新为 async def
(因此使用 await
),那么您需要的可以表示为:
future = asyncio.get_event_loop().create_future()
fn(request=request, callback=future.set_result)
response = await future
可以等待"future"对象,其set_result
方法恢复被等待者。 fn
不需要知道未来,但它只看到一个回调函数。