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