问题:
我正在尝试更新一些旧代码(我没有写过),该代码使用了过时的Tornado和gen.Task
版本,以使用Tornado和asyncio的当前版本。除了这一表达式(我不完全理解(1)我无法弄清楚如何用等效的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
,但不幸的是,它没有指定如何这样做,特别是考虑到{{1 }}需要一个gen.Wait
参数,而key
(显然是concurrent.futures.Futures
的别名)显式地具有 no 方式来支持asyncio.Future
参数。所以我不明白这种说法是可以替代的。
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提出了哪些建议。但是,在版本6的变更日志中,它没有说明如何使用gen.Task
更新我们的代码,只是说它已被删除。我发现at least one question on StackOverflow以及a Tornado GitHub issue都在说(没有给出具体示例或实现细节),gen.Task
的任何实例都可以用gen.Task
代替。但是,由于我不太了解异步编程的一般概念,也不太了解gen.coroutine
的细节,因此,我很难弄清楚该如何完成。不过,这将是很好的,因为用异步等效项替换tornado.gen.Task
似乎很容易-只需gen.coroutine
和async def
即可。
根据文档,await
的结果应该是:
采用一个函数(和可选的其他参数),并使用这些参数和回调关键字参数运行它。传递给回调的参数作为yield表达式的结果返回。
版本4.0中已更改:
的函数。yield gen.Task
现在是一个返回gen.Task
...
但是,这似乎比用Future
可以替代的东西复杂,因为它直接创建了gen.coroutine
,而不是Future
来计算异步函数的结果,并且在asyncio中创建和使用期货的多种方法,我隐约记得在某处读到了龙卷风期货和asyncio期货实际上并不等效。
这涉及异步编程和功能编程的事实使问题变得更加难以理解-我模糊地掌握了功能部分,但是我对异步编程的理解很差,以至于它突然也会使功能性方面现在也很难理解。
到目前为止,我已经尝试过:
await
给出错误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
?
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)
。 The release notes for Tornado 5.0说内部的所有TypeError: Task() takes at most 1 positional arguments (2 given)
都被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
函数在asyncio中创建任务或未来(我不确定我需要创建两个任务),因为使用了{{1}根据asyncio文档,强烈建议不要直接使用}。效果不佳:
create_task
给出错误Task
。
使用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'
的{{1}}方法。
类似的问题:这两个问题看起来很相似,但我不知道如何将其答案用于我的问题。它们可能适用,但是我对异步编程的理解尤其是对异步的理解是非常糟糕的,而且我很愚蠢。因此,对像我五岁这样的其他两个问题的答案进行解释的答案也将不胜感激。对于我的愚蠢和无知,您可以耐心等待。
How does 'yield' work in tornado when making an asynchronous call?
答案 0 :(得分:2)
我找到了至少一个关于StackOverflow的问题,以及一个Tornado GitHub问题,据说该问题(没有给出具体示例或实现细节)可以用gen.coroutine替换gen.Task的任何实例。但是,由于我不太了解异步编程的一般概念,也不太了解tornado.gen.Task的细节,因此,我很难弄清楚该如何完成。不过,这将是很棒的,因为用asyncio等效项替换gen.coroutine似乎很容易-只需def def并等待所有内容即可。
您正在关注“我怎么称呼这个需要回调的东西”。问题是回调的整个概念已被弃用,并已从Tornado中删除,因此没有一种优雅的方法来调用需要回调的东西。前进的预期路径是将需要回调的内容(即fn
)更改为使用gen.coroutine
和/或返回Future
,然后您可以直接从其他协程调用它。
如果fn
使用的是@gen.engine
(龙卷风的协程的第一个版本),这是很容易的:只需将@gen.engine
替换为@gen.coroutine
,然后删除对callback
个参数。该函数可能以callback(response)
结尾;将此内容替换为raise gen.Return(response)
。
如果fn
仅使用了没有@gen.engine
的原始回调,则将其更新为以现代方式工作将变得更加困难,并且需要根据具体情况进行处理所以我不能在这里给你有用的指导。
如果您卡在需要回调的内容而无法更改,则此序列几乎等效于response = yield gen.Task(fn, request)
:
future = tornado.concurrent.Future()
fn(request, callback=future.set_result)
response = yield future
此值与gen.Task
之间的区别与错误处理有关。如果fn
引发了异常,则gen.Task
拥有一些昂贵的魔法来确保它可以捕获该异常并在调用函数中重新引发它。即使对于未使用gen.Task
的应用程序,保持这种魔力也会带来一些性能成本的损失,这就是为什么最终不推荐使用和删除它(以及与回调相关的所有内容)的原因。因此,您可能需要更改fn
以确保捕获并报告了任何可能的异常(再次,建议的操作方式是移至协程,在协程中异常处理的工作会超出您的期望)。
答案 1 :(得分:1)
如果您可以将函数更新为async def
(并因此使用await
),则所需的内容可以表示为:
future = asyncio.get_event_loop().create_future()
fn(request=request, callback=future.set_result)
response = await future
可以等待“ future”对象,它的set_result
方法将恢复被等待的对象。 fn
不需要了解未来,只需要一个回调函数即可。
答案 2 :(得分:-1)
from tornado import ioloop
io_loop = ioloop.IOLoop.current()
response = yield io_loop.add_callback(fn, request)
# ... do something with response