使用 await 或 yield 迭代循环会导致错误
Iterating a loop using await or yield causes error
我来自 Twisted
/Klein
. I come in peace and to ask for Tornado
help. I'm investigating Tornado and how its take on async differs from Twisted. Twisted has something similar to gen.coroutine
which is defer.inlineCallbacks
的土地,我能够编写这样的异步代码:
kleinsample.py
@app.route('/endpoint/<int:n>')
@defer.inlineCallbacks
def myRoute(request, n):
jsonlist = []
for i in range(n):
yield jsonlist.append({'id': i})
return json.dumps(jsonlist)
卷曲命令:
curl localhost:9000/json/2000
此端点将创建一个包含 n
个元素的 JSON 字符串。 n
可以很小也可以很大。我可以在 Twisted 中将其分解,这样事件循环就不会使用 yield
阻塞。下面是我尝试将其转换为 Tornado 的方法:
tornadosample.py
async def get(self, n):
jsonlist = []
for i in range(n):
await gen.Task(jsonlist.append, {'id': i}) # exception here
self.write(json.dumps(jsonlist))
回溯:
TypeError: append() takes no keyword arguments
我对应该如何正确迭代循环中的每个元素以使事件循环不被阻塞感到困惑。有谁知道"Tornado"这样做的方法吗?
我们来看看gen.Task
docs:
Adapts a callback-based asynchronous function for use in coroutines.
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.
由于 append
不接受关键字参数,因此它不知道如何处理该 callback
kwarg 并吐出该异常。
您可以做的是用您自己的函数包装 append
,该函数确实接受 callback
kwarg 或 this 答案中显示的方法。
您不能也不能等待 append
,因为它不是协程,也不是 return 未来。如果您想偶尔屈服以允许其他协同程序继续使用 Tornado 的事件循环,请等待 gen.moment.
from tornado import gen
async def get(self, n):
jsonlist = []
for i in range(n):
jsonlist.append({'id': i})
if not i % 1000: # Yield control for a moment every 1k ops
await gen.moment
return json.dumps(jsonlist)
就是说,除非此功能 非常 CPU 密集并且需要数百毫秒或更长时间才能完成,否则您最好只做所有的事情一次计算,而不是在函数 returns.
之前多次通过事件循环
list.append()
returns None
,所以你的克莱因样本看起来像是在产生一些物体,这有点误导人。这相当于 jsonlist.append(...); yield
作为两个单独的语句。龙卷风等效项是 await gen.moment
代替裸 yield
。
另请注意,在 Tornado 中,处理程序通过调用 self.write()
而不是通过返回值来产生响应,因此 return
语句应为 self.write(json.dumps(jsonlist))
.
我来自 Twisted
/Klein
. I come in peace and to ask for Tornado
help. I'm investigating Tornado and how its take on async differs from Twisted. Twisted has something similar to gen.coroutine
which is defer.inlineCallbacks
的土地,我能够编写这样的异步代码:
kleinsample.py
@app.route('/endpoint/<int:n>')
@defer.inlineCallbacks
def myRoute(request, n):
jsonlist = []
for i in range(n):
yield jsonlist.append({'id': i})
return json.dumps(jsonlist)
卷曲命令:
curl localhost:9000/json/2000
此端点将创建一个包含 n
个元素的 JSON 字符串。 n
可以很小也可以很大。我可以在 Twisted 中将其分解,这样事件循环就不会使用 yield
阻塞。下面是我尝试将其转换为 Tornado 的方法:
tornadosample.py
async def get(self, n):
jsonlist = []
for i in range(n):
await gen.Task(jsonlist.append, {'id': i}) # exception here
self.write(json.dumps(jsonlist))
回溯:
TypeError: append() takes no keyword arguments
我对应该如何正确迭代循环中的每个元素以使事件循环不被阻塞感到困惑。有谁知道"Tornado"这样做的方法吗?
我们来看看gen.Task
docs:
Adapts a callback-based asynchronous function for use in coroutines.
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.
由于 append
不接受关键字参数,因此它不知道如何处理该 callback
kwarg 并吐出该异常。
您可以做的是用您自己的函数包装 append
,该函数确实接受 callback
kwarg 或 this 答案中显示的方法。
您不能也不能等待 append
,因为它不是协程,也不是 return 未来。如果您想偶尔屈服以允许其他协同程序继续使用 Tornado 的事件循环,请等待 gen.moment.
from tornado import gen
async def get(self, n):
jsonlist = []
for i in range(n):
jsonlist.append({'id': i})
if not i % 1000: # Yield control for a moment every 1k ops
await gen.moment
return json.dumps(jsonlist)
就是说,除非此功能 非常 CPU 密集并且需要数百毫秒或更长时间才能完成,否则您最好只做所有的事情一次计算,而不是在函数 returns.
之前多次通过事件循环list.append()
returns None
,所以你的克莱因样本看起来像是在产生一些物体,这有点误导人。这相当于 jsonlist.append(...); yield
作为两个单独的语句。龙卷风等效项是 await gen.moment
代替裸 yield
。
另请注意,在 Tornado 中,处理程序通过调用 self.write()
而不是通过返回值来产生响应,因此 return
语句应为 self.write(json.dumps(jsonlist))
.