1Working with threads
2====================
3
4.. py:currentmodule:: anyio
5
6Practical asynchronous applications occasionally need to run network, file or computationally
7expensive operations. Such operations would normally block the asynchronous event loop, leading to
8performance issues. The solution is to run such code in *worker threads*. Using worker threads lets
9the event loop continue running other tasks while the worker thread runs the blocking call.
10
11 .. caution:: Do not spawn too many threads, as the context switching overhead may cause your
12    system to slow down to a crawl. A few dozen threads should be fine, but hundreds are probably
13    bad. Consider using AnyIO's semaphores to limit the maximum number of threads.
14
15Running a function in a worker thread
16-------------------------------------
17
18To run a (synchronous) callable in a worker thread::
19
20    import time
21
22    from anyio import to_thread, run
23
24
25    async def main():
26        await to_thread.run_sync(time.sleep, 5)
27
28    run(main)
29
30By default, tasks are shielded from cancellation while they are waiting for a worker thread to
31finish. You can pass the ``cancellable=True`` parameter to allow such tasks to be cancelled.
32Note, however, that the thread will still continue running – only its outcome will be ignored.
33
34.. seealso:: :ref:`RunInProcess`
35
36Calling asynchronous code from a worker thread
37----------------------------------------------
38
39If you need to call a coroutine function from a worker thread, you can do this::
40
41    from anyio import from_thread, sleep, to_thread, run
42
43
44    def blocking_function():
45        from_thread.run(sleep, 5)
46
47
48    async def main():
49        await to_thread.run_sync(blocking_function)
50
51    run(main)
52
53.. note:: The worker thread must have been spawned using :func:`~run_sync_in_worker_thread`
54   for this to work.
55
56Calling synchronous code from a worker thread
57---------------------------------------------
58
59Occasionally you may need to call synchronous code in the event loop thread from a worker thread.
60Common cases include setting asynchronous events or sending data to a memory object stream.
61Because these methods aren't thread safe, you need to arrange them to be called inside the event
62loop thread using :func:`~from_thread.run_sync`::
63
64    import time
65
66    from anyio import Event, from_thread, to_thread, run
67
68    def worker(event):
69        time.sleep(1)
70        from_thread.run_sync(event.set)
71
72    async def main():
73        event = Event()
74        await to_thread.run_sync(worker, event)
75        await event.wait()
76
77    run(main)
78
79Calling asynchronous code from an external thread
80-------------------------------------------------
81
82If you need to run async code from a thread that is not a worker thread spawned by the event loop,
83you need a *blocking portal*. This needs to be obtained from within the event loop thread.
84
85One way to do this is to start a new event loop with a portal, using
86:func:`~start_blocking_portal` (which takes mostly the same arguments as :func:`~run`::
87
88    from anyio.from_thread import start_blocking_portal
89
90
91    with start_blocking_portal(backend='trio') as portal:
92        portal.call(...)
93
94If you already have an event loop running and wish to grant access to external threads, you can
95create a :class:`~.BlockingPortal` directly::
96
97    from anyio import run
98    from anyio.from_thread import BlockingPortal
99
100
101    async def main():
102        async with BlockingPortal() as portal:
103            # ...hand off the portal to external threads...
104            await portal.sleep_until_stopped()
105
106    anyio.run(main)
107
108Spawning tasks from worker threads
109----------------------------------
110
111When you need to spawn a task to be run in the background, you can do so using
112:meth:`~.BlockingPortal.start_task_soon`::
113
114    from concurrent.futures import as_completed
115
116    from anyio import sleep
117    from anyio.from_thread import start_blocking_portal
118
119
120    async def long_running_task(index):
121        await sleep(1)
122        print(f'Task {index} running...')
123        await sleep(index)
124        return f'Task {index} return value'
125
126
127    with start_blocking_portal() as portal:
128        futures = [portal.start_task_soon(long_running_task, i) for i in range(1, 5)]
129        for future in as_completed(futures):
130            print(future.result())
131
132Cancelling tasks spawned this way can be done by cancelling the returned
133:class:`~concurrent.futures.Future`.
134
135Blocking portals also have a method similar to :meth:`TaskGroup.start() <.abc.TaskGroup.start>`:
136:meth:`~.BlockingPortal.start_task` which, like its counterpart, waits for the callable to signal
137readiness by calling ``task_status.started()``::
138
139    from anyio import sleep, TASK_STATUS_IGNORED
140    from anyio.from_thread import start_blocking_portal
141
142
143    async def service_task(*, task_status=TASK_STATUS_IGNORED):
144        task_status.started('STARTED')
145        await sleep(1)
146        return 'DONE'
147
148
149    with start_blocking_portal() as portal:
150        future, start_value = portal.start_task(service_task)
151        print('Task has started with value', start_value)
152
153        return_value = future.result()
154        print('Task has finished with return value', return_value)
155
156
157Using asynchronous context managers from worker threads
158-------------------------------------------------------
159
160You can use :meth:`~.BlockingPortal.wrap_async_context_manager` to wrap an asynchronous context
161managers as a synchronous one::
162
163    from anyio.from_thread import start_blocking_portal
164
165
166    class AsyncContextManager:
167        async def __aenter__(self):
168            print('entering')
169
170        async def __aexit__(self, exc_type, exc_val, exc_tb):
171            print('exiting with', exc_type)
172
173
174    async_cm = AsyncContextManager()
175    with start_blocking_portal() as portal, portal.wrap_async_context_manager(async_cm):
176        print('inside the context manager block')
177
178.. note:: You cannot use wrapped async context managers in synchronous callbacks inside the event
179          loop thread.
180
181Context propagation
182-------------------
183
184When running functions in worker threads, the current context is copied to the worker thread.
185Therefore any context variables available on the task will also be available to the code running
186on the thread. As always with context variables, any changes made to them will not propagate back
187to the calling asynchronous task.
188
189When calling asynchronous code from worker threads, context is again copied to the task that calls
190the target function in the event loop thread. Note, however, that this **does not work** on asyncio
191when running on Python 3.6.
192