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()
Exiting a with ThreadPoolExecutor block waits (synchronously!) for all tasks on the executor to finish. You can't shut down an executor while the IOLoop is running; just make a global run and let it run forever.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With