0

I'm looking to define a new connection as shown here: https://udsoncan.readthedocs.io/en/latest/udsoncan/connection.html#defining-a-new-connection

However, I want to call async code from there (i.e. make the send/wait asynchronous). I can't seem to get it working.

Consider the following example as what I am trying to achieve:

import asyncio

async def some_task():
    await asyncio.sleep(1)  # Async task
    print("Done")
    
def sync_method():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.sleep(5)) # Some async event
    print("sure")

async def main():
    sync_method()
    await asyncio.gather(some_task(), some_task(), some_task())
    

if __name__ == "__main__":
    asyncio.run(main())

But this throws an error (sleep was never awaited; but if I await it then I get error of await called from non async function). But I read (see here) that this was how to call an async function from a sync function.

So, essentially my code is asynchronous and main is run in the event loop. Now, I want to call the synchronous function but essentially want to make it non-blocking by using async methods.

What am I doing wrong?

Edit - traceback as requested...

Traceback (most recent call last):
  File ".\tmp_sync_async.py", line 18, in <module>
    asyncio.run(main())
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1520.0_x64__qbz5n2kfra8p0\lib\asyncio\runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1520.0_x64__qbz5n2kfra8p0\lib\asyncio\base_events.py", line 616, in run_until_complete
    return future.result()
  File ".\tmp_sync_async.py", line 13, in main
    sync_method()
  File ".\tmp_sync_async.py", line 9, in sync_method
    loop.run_until_complete(asyncio.sleep(3))
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1520.0_x64__qbz5n2kfra8p0\lib\asyncio\base_events.py", line 592, in run_until_complete
    self._check_running()
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1520.0_x64__qbz5n2kfra8p0\lib\asyncio\base_events.py", line 552, in _check_running
    raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running
sys:1: RuntimeWarning: coroutine 'sleep' was never awaited

Update: From what I found, the run_until_complete way is fine if starting with sync code and then trying to run async code (i.e. if main() in my case was sync code).

SimpleOne
  • 1,066
  • 3
  • 12
  • 29
  • Show the full traceback as properly formatted text in the question. – Michael Butscher Sep 13 '20 at 21:58
  • You are attempting to use a non-async framework (at least judging by its [usage examples](https://udsoncan.readthedocs.io/en/latest/udsoncan/examples.html)) in an async program. That won't work - for asyncio to work, all parts of the program must use it. – user4815162342 Sep 14 '20 at 09:16

1 Answers1

0

You can use a generator to return the coroutine:

import asyncio


async def some_task():
    await asyncio.sleep(1)  # Async task
    print("Done")

    
def sync_method():
    # A yield statement converts a function into a generator
    # The yield statement effectively freezes the function and no code below a yield is executed until the next item is requested. The earlier asyncio implementations were actually done using yield statements.
    yield asyncio.sleep(5) # Some async event
    print("sure")


async def run_promises(future_generator):
    # Fetch the items from the generator one by one
    for future in future_generator:
        # Wait for the future result, the next yield is not requested until this is done
        await future


async def main():
    # To run in the background:
    asyncio.create_task(run_promises(sync_method()))

    # To wait:
    await run_promises(sync_method())

    await asyncio.gather(some_task(), some_task(), some_task())
    

if __name__ == "__main__":
    asyncio.run(main())
Wolph
  • 78,177
  • 11
  • 137
  • 148
  • @SimpleOne how about this solution? that should work without an issue – Wolph Sep 13 '20 at 23:03
  • Sorry, but still does not work: `expect a list of futures, not generator` – SimpleOne Sep 13 '20 at 23:09
  • @SimpleOne yeah, I noticed that too. I've modified the code in the mean time already :) – Wolph Sep 13 '20 at 23:36
  • That works now, thanks...and I just need to, for example, do ```asyncio.create_task(future)``` in order to make it run independently. And, just to be clear, in the udsoncan ```BaseConnection.specific_send(payload)``` I would have the ```for future in some_other_method()``` and my async code in ```some_other_method()```, correct? Finally, can you explain how your answer works please? – SimpleOne Sep 13 '20 at 23:47
  • @SimpleOne I've added a simple wrapper method to make it easy to run your code either in the background or wait for the results. You should be able to easily use it now. Note that if you're really executing slow/blocking code you can also consider using `run_in_executor`: https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor – Wolph Sep 13 '20 at 23:59
  • I looked at ```run_in_executor``` but as I understand it that just runs the sync code in a different thread. I will need to think if that fits what I am trying to do – SimpleOne Sep 14 '20 at 10:54
  • The main advantage of the `run_in_executor` is that it won't slow your other async code. If the sync code block has some slow bits in it, it will effectively freeze your async code completely. – Wolph Sep 14 '20 at 11:57