1
1
"""Async utilities"""
2
2
import asyncio
3
+ import concurrent .futures
3
4
import inspect
4
- from concurrent . futures import ThreadPoolExecutor
5
+ import threading
5
6
from functools import partial
6
7
8
+ from tornado .ioloop import IOLoop
9
+
10
+
11
+ def _asyncio_run (coro ):
12
+ """Like asyncio.run, but works when there's no event loop"""
13
+ # for now: using tornado for broader compatibility with FDs,
14
+ # e.g. when using the only partially functional default
15
+ # Proactor on windows
16
+ loop = IOLoop ()
17
+ return loop .run_sync (lambda : asyncio .ensure_future (coro ))
18
+
7
19
8
20
class AsyncFirst :
9
21
"""Wrapper class that defines synchronous `_sync` method wrappers
@@ -16,13 +28,30 @@ class AsyncFirst:
16
28
17
29
_async_thread = None
18
30
31
+ def _thread_main (self ):
32
+ asyncio_loop = asyncio .new_event_loop ()
33
+ asyncio .set_event_loop (asyncio_loop )
34
+ loop = self ._thread_loop = IOLoop .current ()
35
+ loop .add_callback (self ._loop_started .set )
36
+ loop .start ()
37
+
19
38
def _in_thread (self , async_f , * args , ** kwargs ):
20
39
"""Run an async function in a background thread"""
21
40
if self ._async_thread is None :
22
- self ._async_thread = ThreadPoolExecutor (1 )
23
- future = self ._async_thread .submit (
24
- lambda : asyncio .run (async_f (* args , ** kwargs ))
25
- )
41
+ self ._loop_started = threading .Event ()
42
+ self ._async_thread = threading .Thread (target = self ._thread_main , daemon = True )
43
+ self ._async_thread .start ()
44
+ self ._loop_started .wait (timeout = 5 )
45
+
46
+ future = concurrent .futures .Future ()
47
+
48
+ async def thread_callback ():
49
+ try :
50
+ future .set_result (await async_f (* args , ** kwargs ))
51
+ except Exception as e :
52
+ future .set_exception (e )
53
+
54
+ self ._thread_loop .add_callback (thread_callback )
26
55
return future .result ()
27
56
28
57
def _synchronize (self , async_f , * args , ** kwargs ):
@@ -31,10 +60,16 @@ def _synchronize(self, async_f, *args, **kwargs):
31
60
Uses asyncio.run if asyncio is not running,
32
61
otherwise puts it in a background thread
33
62
"""
34
- if asyncio .get_event_loop ().is_running ():
63
+ try :
64
+ loop = asyncio .get_event_loop ()
65
+ except RuntimeError :
66
+ # sometimes get returns a RuntimeError
67
+ # if there's no current loop under certain policies
68
+ loop = None
69
+ if loop and loop .is_running ():
35
70
return self ._in_thread (async_f , * args , ** kwargs )
36
71
else :
37
- return asyncio . run (async_f (* args , ** kwargs ))
72
+ return _asyncio_run (async_f (* args , ** kwargs ))
38
73
39
74
def __getattr__ (self , name ):
40
75
if name .endswith ("_sync" ):
0 commit comments