Using contextvar to keep track of async loop in Python
According to the asyncio
documentation:
Tasks support the contextvars module. When a Task is created it copies the current context and later runs its coroutine in the copied context.
So, if you declare at the top of your program cvar = contextvars.ContextVar('cvar', default='x')
, when you create a task, this will copy the current context, and if you modify cvar
whithin it will just affect the copy but no the original context. That's the main reason why you got ''
(empty string) at your final output.
To achieve the "track" you want you must use a global variable in order to modify it anywhere. But if you want to play around with asyncio
and contextvars
to see how it works, see the example below:
import asyncio
import contextvars
import time
output = contextvars.ContextVar('output', default='No changes at all')
async def sleep():
print(f'Time: {time.time() - start:.2f}')
await asyncio.sleep(1)
async def sum(name, numbers):
total = 0
for number in numbers:
print(f'Task {name}: Computing {total}+{number}')
await sleep()
total += number
output.set(output.get()+name) #Here we modify the respective context
print(f'Task {name}: Sum = {total}\n')
print(f'Partial output from task {name}:', output.get())
return output.get() #Here we return the variable modified
start = time.time()
# main() will have its own copy of the context
async def main():
output.set('Changed - ') # Change output var in this function context
# task1 and task2 will copy this context (In this contect output=='Changed - ')
task1 = asyncio.create_task(sum("A", [1, 2])) #This task has its own copy of the context of main()
task2 = asyncio.create_task(sum("B", [1, 2, 3])) #This task also has its own copy of the context of main()
done, pending = await asyncio.wait({task1,task2})
resultTask1 = task1.result() # get the value of return of task1
resultTask2 = task2.result() # get the value of return of task1
print('Result1: ', resultTask1)
print('Result2: ', resultTask2)
print('Variable output in main(): ',output.get()) # However, output in main() is sitill 'Changed - '
output.set(output.get()+'/'+resultTask1+'/'+resultTask2) #Modify the var in this context
print('Variable modified in main(): ', output.get())
return output.get() #Return modified value
x = asyncio.run(main()) # Assign the return value to x
end = time.time()
print(f'Time: {end-start:.2f} sec')
print("Final output (without changes) =", output.get())
output.set(x)
print("Final output (changed) =", output.get())
##### OUTPUT #####
# Time: 0.00
# Task B: Computing 0+1
# Time: 0.00
# Task A: Computing 1+2
# Time: 1.01
# Task B: Computing 1+2
# Time: 1.01
# Task A: Sum = 3
# Partial output from task A: Changed - AA
# Task B: Computing 3+3
# Time: 2.02
# Task B: Sum = 6
# Partial output from task B: Changed - BBB
# Result1: Changed - AA
# Result2: Changed - BBB
# Variable output in main(): Changed -
# Variable modified in main(): Changed - /Changed - AA/Changed - BBB
# Time: 3.03 sec
# Final output (without changes) = No changes at all
# Final output (changed) = Changed - /Changed - AA/Changed - BBB
As you can see, it is impossible to modify the same variable at the same time. While task1
is modifying its copy, task2
is modifying its copy too.