[英]Multiple Async HTTP connections to Tornado Server
I have a tornado server which I am trying to make synchronous. 我有一个试图使之同步的龙卷风服务器。 I have a client which makes asynchronous requests to the server simultaneously. 我有一个客户端,它同时向服务器发出异步请求。 It pings the server every 5 seconds with a heartbeat and secondly, it makes a GET request for a job whenever it can. 它每5秒钟使用一次心跳ping服务器一次,其次,它会在可能的情况下发出GET请求。
On the server side, there is a thread-safe queue which contains jobs. 在服务器端,有一个包含作业的线程安全队列。 It blocks for 20 seconds if the queue is empty. 如果队列为空,它将阻塞20秒。 I want it to hold the connection and block for that 20 seconds and when it returns, it writes "No job" to the client. 我希望它保持该连接并阻塞20秒钟,当它返回时,它会向客户端写入“无作业”。 As soon as a job is available, it should immediately write it to the client since queue.get() would return. 作业可用后,应立即将其写入客户端,因为queue.get()将返回。 I want the heartbeats to continue happening in the background while this request is blocked. 我希望在此请求被阻止的同时,心跳继续在后台发生。 Here I am making two asynchronous requests to the server from the same client. 在这里,我正在从同一客户端向服务器发出两个异步请求。
Here is a sample project I build which kind of simulates my issue. 这是我构建的一个示例项目,可以模拟我的问题。
Server: 服务器:
import tornado.ioloop
import tornado.web
from queue import Queue
from tornado import gen
q = Queue()
class HeartBeatHandler(tornado.web.RequestHandler):
@gen.coroutine
def post(self):
print("Heart beat")
class JobHandler(tornado.web.RequestHandler):
@gen.coroutine
def get(self):
print("Job")
try:
job = yield q.get(block=True, timeout=20)
self.write(job)
except Exception as e:
self.write("No job")
def make_app():
return tornado.web.Application([
(r"/heartbeat", HeartBeatHandler),
(r"/job", JobHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
try:
tornado.ioloop.IOLoop.current().start()
except KeyboardInterrupt:
tornado.ioloop.IOLoop.current().stop()
Client: 客户:
import asyncio
from tornado import httpclient, gen
@gen.coroutine
def heartbeat_routine():
while True:
http_client = httpclient.AsyncHTTPClient()
heartbeat_request = httpclient.HTTPRequest("http://{}/heartbeat".format("127.0.0.1:8888"), method="POST",
body="")
try:
yield http_client.fetch(heartbeat_request)
yield asyncio.sleep(5)
except httpclient.HTTPError as e:
print("Heartbeat failed!\nError: {}".format(str(e)))
http_client.close()
@gen.coroutine
def worker_routine():
while True:
http_client = httpclient.AsyncHTTPClient(defaults=dict(request_timeout=180))
job_request = httpclient.HTTPRequest("http://{}/job".format("127.0.0.1:8888"), method="GET")
try:
response = yield http_client.fetch(job_request)
print(response.body)
except httpclient.HTTPError as e:
print("Heartbeat failed!\nError: {}".format(str(e)))
http_client.close()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
asyncio.ensure_future(heartbeat_routine())
asyncio.ensure_future(worker_routine())
loop.run_forever()
Questions: 问题:
If you use a thread-safe queue, you must use not use blocking operations from the IOLoop thread. 如果使用线程安全队列,则必须不使用IOLoop线程的阻塞操作。 Instead, run them in a thread pool: 而是在线程池中运行它们:
job = yield IOLoop.current().run_in_executor(None, lambda: q.get(block=True, timeout=20))
Alternately, you could use Tornado's async (but thread-unsafe) queue, and use IOLoop.add_callback
whenever you need to interact with the queue from another thread. 或者,您可以使用Tornado的异步(但线程不安全)队列,并在需要与另一个线程的队列进行交互时使用IOLoop.add_callback
。
There's some magic in the AsyncHTTPClient
constructor, which tries to share existing instances when possible, but this means that constructor arguments are only effective the first time. AsyncHTTPClient
构造函数中有一些魔术,它尽可能地共享现有实例,但这意味着构造函数参数仅在第一次生效。 The worker_routine
is picking up the default instances created by heartbeat_routine
. worker_routine
正在拾取heartbeat_routine
创建的默认实例。 Add force_instance=True
to ensure you get a fresh client in worker_routine
(and call .close()
on it when you're done) 添加force_instance=True
以确保您在worker_routine
获得了一个新客户端(并在完成后对其调用.close()
)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.