我有各种各样的函数,它们使用阻塞I / O来获取大量数据,并且可以将它写成流/文件类对象(带有分块等);我也有龙卷风HTTP服务器来向客户提供这些数据。至于我无法将所有数据存储在内存中,我应该将其流式传输给客户端,因为我从源代码接收它。所以我写了类似的东西:
import logging
logging.basicConfig(level=logging.DEBUG)
from concurrent.futures import ThreadPoolExecutor
from tornado import gen, httpserver, httpclient, web, ioloop, httputil, escape, locks, iostream
from threading import Event
def get_data(stream):
with open('/tmp/qq.dat') as file:
for chunk in iter(lambda: file.read(64*1024), b''):
stream.write(chunk)
class ProxyStream(object):
def __init__(self, request):
self._request = request
def write(self, data):
self._request.write(data)
event = Event()
self._request.flush(callback=lambda: event.set())
event.wait()
return len(data)
class Test(web.RequestHandler):
def initialize(self, workers):
self._workers = workers
@gen.coroutine
def get(self):
stream = ProxyStream(self)
yield self._workers.submit(get_data, stream)
logging.debug("GET done")
self.finish()
if __name__ == '__main__':
workers = ThreadPoolExecutor(4)
app = web.Application([
(r"/test", Test, {'workers': workers}),
])
server = httpserver.HTTPServer(app) server.bind(1488)
server.start(1)
ioloop.IOLoop.current().start()
上面的代码,get_data()函数读取一些文件(可能非常大)并将其写入块中以作为参数传递。 Stream由ProxyStream对象模拟,该对象将接收的数据写入RequestHandler对象,等待块刷新到网络。
看来这段代码按预期工作,但我仍然怀疑这种方法是否存在一些陷阱,或者可能有更好的方法来解决这个问题?
答案 0 :(得分:0)
实际上我遇到了一些问题,导致我找到对我来说更好的解决方案。
RequestHandler write()
和flush()
方法不是线程安全的,应该从正在运行ioloop
的线程中调用(请参阅this和valid jwt token)。所以正确的方法是包裹write()
& flush()
进入IOLoop.add_callback
,以便在下一次ioloop
次迭代时调用它们。
结果代码是这样的:
class ProxyStream(object):
def __init__(self, handler, headers=[]):
self._handler = handler
def sync_write(self, data, event):
self._handler.write(data)
self._handler.flush(callback=lambda: event.set())
def write(self, data):
if not self._handler.dead:
event = Event()
IOLoop.current().add_callback(self.sync_write, data, event)
event.wait()
return len(data)
else:
raise IOError("Client has closed connection")
(RequestHandler
将自己传递给某些同步代码,应将self.dead = True
设置为on_connection_close()
,以便在客户端断开连接后停止流式传输。