Repository: ray-project/ray

Issue:
Ray async api is not working with uvloop.
<!--Please include [tune], [rllib], [autoscaler] etc. in the issue title if relevant-->

### What is the problem?

Current Ray async api uses asyncio event loop's internal attribute to identify if the loop is running in the current current thread.

```python3
    loop = asyncio.get_event_loop()
    if loop.is_running():
        if loop._thread_id != threading.get_ident():
            # If the loop is runing outside current thread, we actually need
            # to do this to make sure the context is initialized.
            asyncio.run_coroutine_threadsafe(_async_init(), loop=loop)
```
This causes a problem when we uses Ray APIs inside Fast API because Fast API uses uvloop as its main event loop, and uvloop doesn't have `_thread_id` attribute.

@simon-mo Any good idea to fix this? It doesn't seem to be trivial. What about we do async_init() whenever asyncio loop is created in a different thread instead of checking if the event loop's thread id? I assume the only use case where asyncio loop is defined in a different thread is only inside async actor? 

### Reproduction (REQUIRED)
Please provide a script that can be run to reproduce the issue. The script should have **no external library dependencies** (i.e., use fake or mock data / environments):

```python3
import time
import asyncio 
​
import ray
import psutil
from fastapi import FastAPI, APIRouter
​
​
app = FastAPI(
    title="API template",
    description="Template to build upon for API serving and distributed computation",
    version="0.1.0",
    openapi_url="/openapi.json",
    docs_url="/docs",
)
​
@app.on_event("startup")
def startup_event():
    ray.init(num_cpus=2)
​
​
​
@app.on_event("shutdown")
def shutdown_event():
    ray.shutdown()
​
​
@app.get('/async')
async def non_seq_async_process():
    """
    async distributed execution
    """
    @ray.remote
    def slow_function(i):
        time.sleep(i)
        return i
​
    start_time = time.time()
​
    # result_ids = []
    # for i in range(10, 60, 10):
    #     result_ids.append(slow_function.remote(i))
    
    # results = ray.get(result_ids)
​
    results = await asyncio.wait([slow_function.remote(i) for i in range(10, 60, 10)])
​
    
    duration = time.time() - start_time
    out = "Executing the for loop took {:.3f} seconds.\n".format(duration)
    out += f"The results are: {results}\n"

```

If we cannot run your script, we cannot fix your issue.

- [x] I have verified my script runs in a clean environment and reproduces the issue.
- [x] I have verified the issue also occurs with the [latest wheels](https://docs.ray.io/en/latest/installation.html).



Discussion:

Resolving patch:
diff --git a/python/ray/experimental/async_api.py b/python/ray/experimental/async_api.py
--- a/python/ray/experimental/async_api.py
+++ b/python/ray/experimental/async_api.py
@@ -1,7 +1,4 @@
-# Note: asyncio is only compatible with Python 3
-
 import asyncio
-import threading
 
 import ray
 from ray.experimental.async_plasma import PlasmaEventHandler
@@ -10,7 +7,10 @@
 handler = None
 
 
-async def _async_init():
+def init():
+    """Initialize plasma event handlers for asyncio support."""
+    assert ray.is_initialized(), "Please call ray.init before async_api.init"
+
     global handler
     if handler is None:
         worker = ray.worker.global_worker
@@ -20,31 +20,6 @@ async def _async_init():
         logger.debug("AsyncPlasma Connection Created!")
 
 
-def init():
-    """
-    Initialize synchronously.
-    """
-    assert ray.is_initialized(), "Please call ray.init before async_api.init"
-
-    # Noop when handler is set.
-    if handler is not None:
-        return
-
-    loop = asyncio.get_event_loop()
-    if loop.is_running():
-        if loop._thread_id != threading.get_ident():
-            # If the loop is runing outside current thread, we actually need
-            # to do this to make sure the context is initialized.
-            asyncio.run_coroutine_threadsafe(_async_init(), loop=loop)
-        else:
-            async_init_done = asyncio.get_event_loop().create_task(
-                _async_init())
-            # Block until the async init finishes.
-            async_init_done.done()
-    else:
-        asyncio.get_event_loop().run_until_complete(_async_init())
-
-
 def as_future(object_id):
     """Turn an object_id into a Future object.
 


Test patch:
diff --git a/python/ray/tests/test_asyncio.py b/python/ray/tests/test_asyncio.py
--- a/python/ray/tests/test_asyncio.py
+++ b/python/ray/tests/test_asyncio.py
@@ -113,8 +113,8 @@ async def test_asyncio_get(ray_start_regular_shared, event_loop):
     loop.set_debug(True)
 
     # This is needed for async plasma
-    from ray.experimental.async_api import _async_init
-    await _async_init()
+    from ray.experimental.async_api import init
+    init()
 
     # Test Async Plasma
     @ray.remote