Add new synchronization primitives to asyncio

Asyncio’s synchronization primitives Event and Lock are a bit difficult to use, so I want to add a new synchronization method. It looks like this:

class Key:
    def __init__(self) -> None:
        self._enter = asyncio.Lock()
        self._exit = asyncio.Lock()
        self._enter._locked = True
        self._exit._locked = True

    async def unlock(self):
        self._enter.release()
        await self._exit.acquire()

    async def __aenter__(self):
        await self._enter.acquire()

    async def __aexit__(self,exc,excv,track):
        self._exit.release()

The async with statement will wait before the unlock call, and unlock will also wait until the async with statement exits. If you use unlock in coroutine A and async with in coroutine B, the execution order will be: the part before the unlock call → the part in async with → the part after the unlock call. This is much more convenient than using Events repeatedly.

Just a technical suggestion: We can’t call it a synchronization primitive because it makes use of existing synchronization primitives. I would suggest calling it a ‘Task Coordinator.’

1 Like

I don’t think that’s fair, the Barrier synchronization primitive uses the Condition primitive, and it’s still one itself

I agree that this isn’t a primitive; your entire purpose here is to use existing primitives to make something that’s easier to use. What I’m less clear on is the conceptual meaning here, and why it’s called a “Key”. Can you elaborate?

Because it behaves like a default locked lock, it will block the code in async with, which is similar to the behavior of Lock. However, a “key” is needed to unlock the statements in Async with, and the key cannot be pulled out before async with is executed (it will be blocked). This behavior is like a real key

What are the use cases for this functionality?

So far you’ve only proposed a new class, but have not explained how this will improve code (e.g. which code patterns will get clearer with the new functionality, ideally by pointing to real(fistic) code instead of toy examples).

I think this is useful in one-to-one or one-to-many collaborative coroutines, as it can stipulate that the async withcode block must be fully executed when unlock is called. If you don’t use this method, you have to set an Event at the start and an Event at the end, and you can’t intuitively see where it starts and ends. To ensure that it ends when an exception occurs, you have to use try... except... This is tantamount to adding insult to injury for the already complex asynchronous programming.

Can you show a code example?

This is where I’m going to use this new synchronization method. I had to shorten it because the code was too long.

import asyncio,httpx
client = httpx.AsyncClient()
url = 'example.com'
file =None

class Key:
    ...

async def download_block(start_pos,save_info = None):
    headers =#build requests headers
    async with client.stream('GET',url,headers=headers) as re:
        if save_info is not None:
            re_header = re.headers
            await save_info.unlock()#async with must wait for unlock(), and unlock() will also wait for async with to complete
        async for i in re.aiter_raw():
            file.seek_and_write(i)

async def download_main(url):
    save_info = Key()
    asyncio.create_task(download_block(0,save_info))

    async with save_info:
        #get file_size,file_name,ableToParallelDownload from re_head
        file = open(filename,'w+b')

    if ableToParallelDownload:
        #create more download_block task,and end the task at the appropriate time
asyncio.run(download_main(url))