Here is the demo code.
import threading
import time
from tornado.concurrent import Future
from tornado import gen, ioloop
@gen.coroutine
def foo(url):
future = Future()
threading.Thread(target=fetch_url_wrapped, args=(url, future)).start()
res = yield future
print(res)
def fetch_url(url):
time.sleep(1)
return 'hello world'
def fetch_url_wrapped(url, future):
print('fetching {}'.format(url))
result = fetch_url(url)
future.set_result(result)
print('set result {}'.format(result))
loop = ioloop.IOLoop.current()
loop.run_sync(lambda: foo('http://bar.com'))
Before Tornado 5.0.0, it is ok.
After version 5.0.0 (including 5.0.0), the program just blocks.
Futures are not thread-safe. see #2308
Found a solution.
import threading
import time
from tornado import gen, ioloop
try:
from concurrent.futures import Future
except:
from tornado.concurrent import Future
@gen.coroutine
def foo(url):
future = Future()
threading.Thread(target=fetch_url_wrapped, args=(url, future)).start()
res = yield future
print(res)
def fetch_url(url):
time.sleep(1)
return 'hello world'
def fetch_url_wrapped(url, future):
print('fetching {}'.format(url))
result = fetch_url(url)
future.set_result(result)
print('set result {}'.format(result))
loop = ioloop.IOLoop.current()
loop.run_sync(lambda: foo('http://bar.com'))
Your toy example makes it sound as if what you want to do is fetch data from a URL. You don't need to use a separate thread, but rather can use Tornado's AsyncHTTPClient to asynchronously fetch data on the event loop.
If what you want to do is use some API that is not async aware and so wish to wrap in a thread, don't use threads and futures directly. Instead you should use IOLoop.run_in_executor.
I was considering to use IOLoop.run_in_executor for wrapping a synchronous function.
But I just need one thread, set max_workers = 1 for ThreadPoolExecutor? But does it sound like a pool?
So I decide to just wrap it in one thread.
ThreadPoolExecutor won't create more threads than necessary, so it's fine to use run_in_executor for just one thread. (Also, threads aren't that expensive, so it's not generally a problem to let the executor create a thread for each CPU)
The reason your original code stopped working is that the only method in Tornado that is guaranteed to be safe to call from other threads is IOLoop.add_callback. Future.set_result happened to be thread-safe in 4.5, but it's no longer thread-safe in 5.0. If you need a thread-safe Future, you must use concurrent.futures.Future instead of tornado.concurrent.Future or asyncio.Future.
Most helpful comment
ThreadPoolExecutorwon't create more threads than necessary, so it's fine to userun_in_executorfor just one thread. (Also, threads aren't that expensive, so it's not generally a problem to let the executor create a thread for each CPU)The reason your original code stopped working is that the only method in Tornado that is guaranteed to be safe to call from other threads is IOLoop.add_callback. Future.set_result happened to be thread-safe in 4.5, but it's no longer thread-safe in 5.0. If you need a thread-safe Future, you must use
concurrent.futures.Futureinstead oftornado.concurrent.Futureorasyncio.Future.