29

After perusing many docs on AsyncIO and articles I still could not find an answer to this : Run a function asynchronously (without using a thread) and also ensure the function calling this async function continues its execution.

Pseudo - code :

async def functionAsync(p):
    #...
    #perform intensive calculations
    #...
    print ("Async loop done")

def functionNormal():
    p = ""
    functionAsync(p)
    return ("Main loop ended")

 print ("Start Code")
 print functionNormal()

Expected Output :

Start code
Main loop ended
Async loop done

Searched examples where loop.run_until_complete is used, but that will not return the print value of functionNormal() as it is blocking in nature.

Dan Swain
  • 2,910
  • 1
  • 16
  • 36
Shivansh Jagga
  • 1,541
  • 1
  • 15
  • 24
  • If it should run "without using a thread", how do you expect this to work? Or, are you saying that it's ok for the _implementation_ to use threads under the hood, but you don't want to create a thread explicitly? – user4815162342 Apr 12 '19 at 09:21
  • Yes that is what I mean. Explicitly I do not want to create a thread. If it does it under the hood it's fine (which acc to my knowledge it might not be, as per my reading concurrency does not always mean a new thread.) – Shivansh Jagga Apr 12 '19 at 09:55
  • Concurrency doesn't always mean a new thread **if** you use coroutines (`async def`) for all your code. But your requirement is to have a sync function executed concurrently with async code, and that will certainly require multiple threads or fibers. – user4815162342 Apr 12 '19 at 11:05
  • 1
    Async code can be started in a new event loop too if i'm correct. loop = `asyncio.new_event_loop()` ..And yes you are right, the sync code should continue running and go to the next line of code as shown in the example. – Shivansh Jagga Apr 12 '19 at 11:18
  • 2
    `new_event_loop` only allocates an event loop. To actually run async code in it, you must use `run_until_complete` or `run_forever`, which blocks the current thread - so you need an additional thread to run sync code concurrently with async code. It will never work without threads. – user4815162342 Apr 12 '19 at 11:43
  • I need to use context locals of `request.args` from flask . This is why I did not want to create a new thread. – Shivansh Jagga Apr 12 '19 at 12:45

5 Answers5

11

asyncio can't run arbitrary code "in background" without using threads. As user4815162342 noted, in asyncio you run event loop that blocks main thread and manages execution of coroutines.

If you want to use asyncio and take advantage of using it, you should rewrite all your functions that uses coroutines to be coroutines either up to main function - entry point of your program. This main coroutine is usually passed to run_until_complete. This little post reveals this topic in more detail.


Since you're interested in Flask, take a look Quart: it's a web framework that tries to implement Flask API (as much as it's possible) in terms of asyncio. Reason this project exists is because pure Flask isn't compatible with asyncio. Quart is written to be compatible.

If you want to stay with pure Flask, but have asynchronous stuff, take a look at gevent. Through monkey-patching it can make your code asynchronous. Although this solution has its own problems (which why asyncio was created).

Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
11

Maybe it's a bit late, but I'm running into a similar situation and I solved it in Flask by using run_in_executor:

def work(p):
    # intensive work being done in the background


def endpoint():
    p = ""
    loop = asyncio.get_event_loop()
    loop.run_in_executor(None, work, p)

I'm not sure however how safe this is since the loop is not being closed.

akch
  • 119
  • 1
  • 2
10

Just use asyncio.run() inside a synchronous function.

def syncfunc():
    async def asyncfunc():
        await some_async_work()
    asyncio.run(asyncfunc())

syncfunc()

Note that asyncio.run() cannot be called when another asyncio event loop is running in the same thread.

TakuMine
  • 131
  • 2
  • 5
9

Here is an implementation of a helper function which you can use like this:

result = synchronize_async_helper(some_async_function(parmater1,parameter2))
import asyncio


def synchronize_async_helper(to_await):
    async_response = []

    async def run_and_capture_result():
        r = await to_await
        async_response.append(r)

    loop = asyncio.get_event_loop()
    coroutine = run_and_capture_result()
    loop.run_until_complete(coroutine)
    return async_response[0]

eshalev
  • 3,033
  • 4
  • 34
  • 48
0

Assuming the synchronous function is inside an asynchronous function, you can solve it using exceptions. Pseudo code:

class CustomError(Exception):
pass


async def main():
    def test_syn():
        time.sleep(2)
        # Start Async
        raise CustomError
    try:
        test_syn()
    except CustomError:
        await asyncio.sleep(2)
    
Ramo
  • 1
  • 1