2016-10-16 2 views
0

Ich bin Neuling auf Tornado und Python. Vor ein paar Tagen fing ich an, eine nicht blockierende Ruhe-API zu schreiben, aber ich konnte die Mission noch nicht erfüllen. Wenn ich zwei Anfragen gleichzeitig an diesen Endpunkt "localhost: 8080/async" sende, reagiert die zweite Anfrage nach 20 Sekunden! Das erklärt, dass ich etwas falsch mache.Python Tornado gen.coroutine Blöcke Anfrage

MAX_WORKERS = 4 
class ASYNCHandler(tornado.web.RequestHandler): 
    executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) 
    counter = 0 

    def pow_task(self, x, y): 
     time.sleep(10) 
     return pow(x,y) 

    async def background_task(self): 
     future = ASYNCHandler.executor.submit(self.pow_task, 2, 3) 
     return future 

    @gen.coroutine 
    def get(self, *args, **kwargs): 
     future = yield from self.background_task() 
     response= dumps({"result":future.result()}, default=json_util.default) 
     print(response) 


application = tornado.web.Application([ 
     ('/async', ASYNCHandler), 
     ('/sync', SYNCHandler), 
    ], db=db, debug=True) 
application.listen(8888) 
tornado.ioloop.IOLoop.current().start() 

Antwort

0

Das seltsame ist, dass die ThreadPoolExecutor Zukunft zurückkehren, im Wesentlichen Ereignisschleife der Blöcke Tornado. Wenn jemand aus dem Tornado-Team dies liest und weiß, warum das so ist, können sie bitte eine Erklärung geben? Ich hatte vor, ein paar Sachen mit Tornado-Fäden zu machen, aber nachdem ich mich mit dieser Frage beschäftigt habe, sehe ich, dass es nicht so einfach wird, wie ich es ursprünglich erwartet hatte. Auf jedem Fall hier ist der Code, der tut, was Sie erwarten (ich habe Ihr ursprüngliches Beispiel ein wenig nach unten getrimmt, so dass jeder es schnell laufen kann):

from concurrent.futures import ThreadPoolExecutor 
from json import dumps 
import time 
from tornado.platform.asyncio import to_tornado_future 
from tornado.ioloop import IOLoop 
from tornado import gen, web 

MAX_WORKERS = 4 

class ASYNCHandler(web.RequestHandler): 
    executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) 
    counter = 0 

    def pow_task(self, x, y): 
     time.sleep(5) 
     return pow(x,y) 

    async def background_task(self): 
     future = self.executor.submit(self.pow_task, 2, 3) 
     result = await to_tornado_future(future) # convert to tornado future 
     return result 

    @gen.coroutine 
    def get(self, *args, **kwargs): 
     result = yield from self.background_task() 
     response = dumps({"result": result}) 
     self.write(response) 


application = web.Application([ 
     ('/async', ASYNCHandler), 
    ], debug=True) 
application.listen(8888) 
IOLoop.current().start() 

Die wichtigsten Unterschiede sind in der background_tasks() Methode. Ich konvertiere die asyncio Zukunft in eine tornado Zukunft, warte auf das Ergebnis, dann gebe das Ergebnis zurück. Der Code, den Sie in der Frage angegeben haben, wurde aus irgendeinem Grund blockiert, als Sie von background_task() abgegeben haben und Sie konnten das Ergebnis await nicht erreichen, weil die Zukunft kein Tornado war.

Mit einer etwas anderen Anmerkung, dieses einfache Beispiel kann leicht mit einem einzigen Thread/Async-Designs implementiert werden und die Chancen sind, dass Ihr Code auch ohne Threads erfolgen kann. Threads sind einfach zu implementieren, aber ebenso leicht zu verfälschen und können zu sehr klebrigen Situationen führen. Wenn Sie versuchen, threaded Code schreiben, denken Sie daran, this photo :)