> I agree that is counter to the basic philosophy, though there are
potential use cases where it may be unavoidable to use threads. It
seems the best solutions when it is then are to use run_in_executor,
which automatically handles the waking up of the loop.
While this doesn't provide an immediate solution, we're currently working on implementing an asyncio.ThreadPool for Python 3.9, which is made to be more self-contained (initialized and finalized within an async context manager using ``async with``) and with a higher level API compared to loop.run_in_executor(). In general, I'd recommend making use of either of those two compared to directly using threads in asyncio.
That being said, if you need more fine-grained control, there's also one other alternative low-level construct/design pattern you can use for working with threads in asyncio that we use in the implementation of some coroutine methods, such as one that I recently added to 3.9 called loop.shutdown_default_executor(). Using a similar format to your above example:
```
def _do_complete_something(fut, loop, ...):
try:
# perform some action, optionally get a result to set to the future
loop.call_soon_threadsafe(future.set_result, None) # replace None with actual result if needed
except Exception as ex:
loop.call_soon_threadsafe(future.exception, ex)
async def compute_something(...):
# In general, I'd recommend using get_running_loop() to get the event loop within a coroutine function.
# It has more predictable behavior compared to get_event_loop(). (Python 3.7+)
loop = asyncio.get_running_loop()
fut = loop.create_future()
th = threading.Thread(target=_do_compute_something, args=(fut,loop, ...))
try:
# use "return await future" if the value is desired
await future
finally:
th.join()
async def compute_stuff():
result = await compute_something(...)
asyncio.run(compute_stuff())
```
Hopefully that helps a bit, or at least gives you some some ideas.