Understanding Python contextvars

前端 未结 1 591
遥遥无期
遥遥无期 2021-01-21 12:24

Regarding the following SO answer . I\'ve made some changes in order to understand the difference between do use Contextvars and don\'t.

I expect at some point the variab

1条回答
  •  情话喂你
    2021-01-21 13:00

    Context variables are convenient when you need to pass a variable along the chain of calls so that they share a single context, in the case when this cannot be done through a global variable in case of concurrency. Context variables can be used as an alternative to global variables both in multi-threaded code and in asynchronous (with coroutines).

    Context variables are natively supported in asyncio and are ready to be used without any extra configuration. Because when a Task is created it copies the current context and later runs its coroutine in the copied context:

    # asyncio/task.py
    class Task:
        def __init__(self, coro):
            ...
            # Get the current context snapshot.
            self._context = contextvars.copy_context()
            self._loop.call_soon(self._step, context=self._context)
    
        def _step(self, exc=None):
            ...
            # Every advance of the wrapped coroutine is done in
            # the task's context.
            self._loop.call_soon(self._step, context=self._context)
            ...
    

    Below is your example showing the corruption of a global variable:

    import asyncio
    import contextvars
    
    # declare context var
    current_request_id_ctx = contextvars.ContextVar('')
    current_request_id_global = ''
    
    
    async def some_inner_coroutine():
        global current_request_id_global
    
        # simulate some async work
        await asyncio.sleep(0.1)
    
        # get value
        print('Processed inner coroutine of request: {}'.format(current_request_id_ctx.get()))
        if current_request_id_global != current_request_id_ctx.get():
            print(f"ERROR! global var={current_request_id_global}")
    
    
    async def some_outer_coroutine(req_id):
        global current_request_id_global
    
        # set value
        current_request_id_ctx.set(req_id)
        current_request_id_global = req_id
    
        await some_inner_coroutine()
    
        # get value
        print('Processed outer coroutine of request: {}\n'.format(current_request_id_ctx.get()))
    
    
    async def main():
        tasks = []
        for req_id in range(1, 10000):
            tasks.append(asyncio.create_task(some_outer_coroutine(req_id)))
    
        await asyncio.gather(*tasks)
    
    
    if __name__ == '__main__':
        asyncio.run(main())
    

    Output:

    ...
    Processed inner coroutine of request: 458
    ERROR! global var=9999
    Processed outer coroutine of request: 458
    
    Processed inner coroutine of request: 459
    ERROR! global var=9999
    Processed outer coroutine of request: 459
    ...
    

    An example of converting code that uses threading.local() can be found in PЕP 567

    0 讨论(0)
提交回复
热议问题