2

It's clear for my how I use asyncio in python3.

import asyncio
import time

async def do_something(number):
    print(f"do something no.{number} at timestamp      : {time.perf_counter():6.2f}")
    await asyncio.sleep(1)
    print(f"do something no.{number} ended at timestamp: {time.perf_counter():6.2f}")

async def main():
    await asyncio.gather(
        do_something(1),
        do_something(2)
    ) 

asyncio.run(main() )

However, I have no idea how I could create an own "await"-able object like asyncio.sleep. In this "await"-able I could encapsulate urllib.request, isn't it?

Can someone post an example? Many thanks.

Thomas K
  • 42
  • 6

2 Answers2

1

You can await all coroutines, so any function with the prefix async is awaitable

For example:

import asyncio


async def some_function():
    await asyncio.sleep(1)
    print("Hello")


async def main():
    await some_function()


asyncio.run(main())

You can find some more information about coroutines at the python docs: https://docs.python.org/3/library/asyncio-task.html

Because urllib is blocking (does not allow other code to be running before it finishes) by default, it is not that easy to just "wrap" it in an awaitable function.

It is probably possible to offload it to something like another thread and then have an awaiable wait for that thread to finish, but it is probably easier to use an async web request library like aiohttp.

Alve
  • 640
  • 2
  • 9
  • 19
  • Thanks for your quick answer. :) *"Because urllib is blocking (does not allow other code to be running before it finishes) by default, it is not that easy to just "wrap" it in an awaitable function."* Yes, but this is exactly what I want to know and that's also the reason why I added the example with urllib. How would this look like? – Thomas K May 17 '21 at 09:40
  • @ThomasK I found this, idk if it still works but it seems to do something like what you wanted: https://gist.github.com/memee/66f85aea70f49489eb09 – Alve May 17 '21 at 09:44
  • Thank you for the link, that's what I searched for. Do you know the difference to @Mikhail Gerasimov suggestion? – Thomas K May 17 '21 at 11:14
1

Please, take a look at this answer it uses old yield for-based syntax, but the idea stays the same: using asyncio.Future and loop.call_later() you can cast callback-based code into coroutine-based code:

import asyncio


async def my_sleep(delay):
    fut = asyncio.Future()

    loop = asyncio.get_event_loop()
    loop.call_later(
        delay,
        lambda *_: fut.set_result(True)
    )

    return await fut


async def main():
    await asyncio.gather(
        my_sleep(1),
        my_sleep(2),
        my_sleep(3)
    )
    print('ok')


asyncio.run(main())

I believe, urllib.request is blocking and doesn't provide callbacks so it can't be cast into coroutine-based form directly. A common way to handle the situation is to run it in async thread (see links in this answer).

But if you want just to make async http reqeust, forget all above and use aiohttp: it's created for the purpose.

Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
  • Thank you for the example. Would it be possible to use another function which contain the url request instead of the lambda-function? That would be pretty nice because the delay could work as a timeout. – Thomas K May 17 '21 at 11:12
  • @ThomasK no, lambda in the example above is only to mark some future done. It happens instantly. Regular blocking request would just block event loop. If you want async request - use `aiohttp`. If you want async timeout - use [asyncio.wait_for](https://docs.python.org/3/library/asyncio-task.html#asyncio.wait_for), it has a timeout param. – Mikhail Gerasimov May 17 '21 at 15:23