I am trying to find a general solution for offloading blocking tasks to a ThreadPoolExecutor
.
In the example below, I can achieve the desired non-blocking result in the NonBlockingHandler
using Tornado's run_on_executor
decorator.
In the asyncify
decorator, I am trying to accomplish the same thing, but it blocks other calls.
Any ideas how to get the asyncify
decorator to work correctly and not cause the decorated function to block?
NOTE: I am using Python 3.6.8 and Tornado 4.5.3
Here is the full working example:
import asyncio
from concurrent.futures import ThreadPoolExecutor
import functools
import time
from tornado.concurrent import Future, run_on_executor
import tornado.ioloop
import tornado.web
def asyncify(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
result = Future()
def _run_on_executor(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
with ThreadPoolExecutor(max_workers=1) as executor:
return executor.submit(fn, *args, **kwargs)
return wrapper
@_run_on_executor
def _func(*args, **kwargs):
return func(*args, **kwargs)
def on_response(response):
result.set_result(response.result())
future = _func(*args, **kwargs)
future.add_done_callback(on_response)
return await result
return wrapper
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write(f"Hello, {self.__class__.__name__}")
class AsyncifyHandler(tornado.web.RequestHandler):
@asyncify
def get(self):
print("sleeping")
time.sleep(5)
self.write(f"Hello, {self.__class__.__name__}")
class NonBlockingHandler(tornado.web.RequestHandler):
executor = ThreadPoolExecutor(max_workers=1)
@run_on_executor
def blocking(self):
print("sleeping")
time.sleep(5)
self.write(f"Hello, {self.__class__.__name__}")
async def get(self):
result = Future()
publish = self.blocking()
publish.add_done_callback(
lambda response: result.set_result(response.result())
)
return await result
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
(r"/asyncify", AsyncifyHandler),
(r"/noblock", NonBlockingHandler),
], debug=True)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()