Reputation: 779
I have a Python application in which multiple "tasks" will call exec
function to evaluate multiple Python statements using a global "context" dictionary. The application would benefit tremendously by utilizing coroutines, but I could not find a way to create coroutine-specific context (global variables).
A simple example looks like this:
import asyncio
context = {}
async def async_exec(statements):
global context
for stmt in statements:
exec(stmt, context, context)
await asyncio.sleep(0)
def sync_main():
asyncio.run(async_exec(['a=1', 'print(f"{a}==1")']))
asyncio.run(async_exec(['a=2', 'print(f"{a}==2")']))
async def async_main():
return await asyncio.gather(
asyncio.create_task(async_exec(['a=1', 'print(f"{a}==1")'])),
asyncio.create_task(async_exec(['a=2', 'print(f"{a}==2")'])),
)
sync_main()
asyncio.run(async_main())
When I execute the code, I get
1==1
2==2
2==1 <- problem here
2==2
because the actual async execution sequence is
a=1
a=2
print(f"{a}==1")
print(f"{a}==2")
Is there any easy way to save and restore context variables for coroutines?
Many thanks for the suggested use of contextvars
, I am not sure how costly it would be to maintain fairly large dictionaries as contextvars
but the following works for my application:
import asyncio
import contextvars
context = contextvars.ContextVar("global")
async def async_exec(statements):
global context
for stmt in statements:
# retrieving the global dictionary
gv = context.get({})
# use the dictionary to evaluate statement
exec(stmt, gv, gv)
# set global dictionary back to the context
context.set(gv)
await asyncio.sleep(0)
def sync_main():
asyncio.run(async_exec(['a=1', 'print(f"{a}==1")']))
asyncio.run(async_exec(['a=2', 'print(f"{a}==2")']))
async def async_main():
return await asyncio.gather(
asyncio.create_task(async_exec(['a=1', 'print(f"{a}==1")'])),
asyncio.create_task(async_exec(['a=2', 'print(f"{a}==2")'])),
)
sync_main()
asyncio.run(async_main())
with output
1==1
2==2
1==1
2==2
Upvotes: 1
Views: 763
Reputation: 195448
As far as I understand, you can use contextvars
built-in module:
import asyncio
import contextvars
context = contextvars.ContextVar("Some context")
async def async_exec(statements):
global context
for stmt in statements:
exec(stmt, {"context": context}, {"context": context})
await asyncio.sleep(0)
def sync_main():
asyncio.run(async_exec(["context.set(1)", 'print(f"{context.get()}==1")']))
asyncio.run(async_exec(["context.set(2)", 'print(f"{context.get()}==2")']))
async def async_main():
return await asyncio.gather(
asyncio.create_task(
async_exec(["context.set(1)", 'print(f"{context.get()}==1")'])
),
asyncio.create_task(
async_exec(["context.set(2)", 'print(f"{context.get()}==2")'])
),
)
sync_main()
asyncio.run(async_main())
Prints correctly:
1==1
2==2
1==1
2==2
Upvotes: 1