initialized = D.setdefault('initialized', True)
... initialized = D.setdefault('initialized', True)
... D = {}
async def maybe_initialize():
global D
this_setup = asyncio.ensure_future(one_time_setup())
actual_setup = D.setdefault('initializer', this_setup)
if this_setup is not actual_setup:
this_setup.cancel()
await actual_setup
return
complete example: https://gist.github.com/mgraczyk/e251443bccfe54505e75b652655...If you read the full blog post, the author solves the problem without setdefault, and without having to cancel any futures.
It's way easier with a single thread. You don't need a lock or atomic operations.
initializer = None
async def maybe_initialize():
global initializer
if initializer is None:
initializer = asyncio.ensure_future(one_time_setup())
await initializer
return- Chris forgot to declare the variable as global;
- Chris used asyncio.create_task() instead of asyncio.ensure_future().
The problem is that there are three logical states that need three different actions
1. if nothing done yet: start async computation
2. if async computation in progress: wait for it
3. return result as computaton done
Atomicity in the sense that no other Python code can run during a setdefault, is not relevant.
Waiting for something that is running in another loop actually isn't easy either; I'm not sure that Python really allows it - won't you get an error:
The future belongs to a different loop than the one specified as the loop argumentHaving multiple loops waiting for each other is pretty complex to debug.
import asyncio
import threading
future = None
def p(msg):
print(f"{msg}: {threading.get_ident()}, event loop: {id(asyncio.get_running_loop())}")
async def one_time_setup():
p('one_time_setup start')
await asyncio.sleep(1)
p('one_time_setup done')
async def maybe_initialize():
global future
if not future:
future = asyncio.create_task(one_time_setup())
p('waiting start')
await future
p('waiting done')
def worker():
new_loop = asyncio.new_event_loop()
asyncio.set_event_loop(new_loop)
new_loop.run_until_complete(maybe_initialize())
number_of_threads = 2
for _ in range(number_of_threads):
threading.Thread(target=worker).start()
this prints RuntimeError: Task <Task pending coro=<maybe_initialize() running at test.py:20> cb=[_run_until_complete_cb() at /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py:158]> got Future <Task pending coro=<one_time_setup() running at test.py:11> cb=[<TaskWakeupMethWrapper object at 0x10cafdeb8>()]> attached to a different loop