如何用等效的asyncio表达式替换`yield gen.Task(fn,arguments)`?

时间:2019-07-18 22:24:42

标签: asynchronous callback tornado python-asyncio coroutine

问题: 我正在尝试更新一些旧代码(我没有写过),该代码使用了过时的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.Waitgen.Callback已被删除。该文档的早期版本表示gen.Wait已过时,应替换为tornado.concurrent.Futures,但不幸的是,它没有指定如何这样做,特别是考虑到{{1 }}需要一个gen.Wait参数,而key(显然是concurrent.futures.Futures的别名)显式地具有 no 方式来支持asyncio.Future参数。所以我不明白这种说法是可以替代的。

key似乎也不足以满足此目的,因为文档明确指出回调只能接受一个参数,而add_done_callback有两个参数。

尽管到目前为止,最有效的方法(实际上可能是有效的,只要我可以使fngen.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.coroutineasync 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?

Extending tornado.gen.Task

3 个答案:

答案 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)

使用IOLoop.add_callback

from tornado import ioloop

io_loop = ioloop.IOLoop.current()

response = yield io_loop.add_callback(fn, request)
# ... do something with response