Skip to content

bpo-32309: Add support for contextvars in asyncio.to_thread() #20278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Doc/library/asyncio-task.rst
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,9 @@ Running in Threads
Asynchronously run function *func* in a separate thread.

Any \*args and \*\*kwargs supplied for this function are directly passed
to *func*.
to *func*. Also, the current :class:`contextvars.Context` is propogated,
allowing context variables from the event loop thread to be accessed in the
separate thread.

Return an :class:`asyncio.Future` which represents the eventual result of
*func*.
Expand Down Expand Up @@ -657,6 +659,8 @@ Running in Threads
that release the GIL or alternative Python implementations that don't
have one, `asyncio.to_thread()` can also be used for CPU-bound functions.

.. versionadded:: 3.9


Scheduling From Other Threads
=============================
Expand Down
8 changes: 6 additions & 2 deletions Lib/asyncio/threads.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""High-level support for working with threads in asyncio"""

import functools
import contextvars

from . import events

Expand All @@ -12,10 +13,13 @@ async def to_thread(func, /, *args, **kwargs):
"""Asynchronously run function *func* in a separate thread.

Any *args and **kwargs supplied for this function are directly passed
to *func*.
to *func*. Also, the current :class:`contextvars.Context` is propogated,
allowing context variables from the main thread to be accessed in the
separate thread.

Return an asyncio.Future which represents the eventual result of *func*.
"""
loop = events.get_running_loop()
func_call = functools.partial(func, *args, **kwargs)
ctx = contextvars.copy_context()
func_call = functools.partial(ctx.run, func, *args, **kwargs)
return await loop.run_in_executor(None, func_call)
14 changes: 14 additions & 0 deletions Lib/test/test_asyncio/test_threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import asyncio
import unittest

from contextvars import ContextVar
from unittest import mock
from test.test_asyncio import utils as test_utils

Expand Down Expand Up @@ -74,6 +75,19 @@ async def main():
self.loop.run_until_complete(main())
func.assert_called_once_with('test', something=True)

def test_to_thread_contextvars(self):
test_ctx = ContextVar('test_ctx')

def get_ctx():
return test_ctx.get()

async def main():
test_ctx.set('parrot')
return await asyncio.to_thread(get_ctx)

result = self.loop.run_until_complete(main())
self.assertEqual(result, 'parrot')


if __name__ == "__main__":
unittest.main()