Pythonでは、
asyncioを使うと非同期タスクの処理を書くことができて、
以下のページにわかりやすいサンプルもあります。

スレッドまたはプロセスプールでコードを実行する | docs.python.org
https://docs.python.org/ja/3/library/asyncio-eventloop.html#executing-code-in-thread-or-process-pools

ところで、
ファイルやネットワークI/Oをopenして処理するケースでは、
ContextManagerでwith句(非同期の場合はasync with)を使って
書くことができるとわかりやすいと思います。
調べ見ると、以下のページに、
contextlibにasynccontextmanagerというものがあったので、 使ってみたメモを残しておきます。

@contextlib.asynccontextmanager | docs.python.org
https://docs.python.org/ja/3/library/contextlib.html#contextlib.asynccontextmanager

同期タスクの場合

まずは、比較用に同期タスクの場合のコードです。
task_backgroundという関数を動かして、
I/O処理中にバックグラウンドの処理が行われるかを見えるようにしています。

study1.py

import asyncio

def blocking_io():
    with open('/dev/urandom', 'rb') as f:
        return f.read(100)

async def main():
    async def task_background():
        while True:
            print("background task")
            await asyncio.sleep(0)
    task = asyncio.create_task(task_background())

    print("begin")
    result = blocking_io()
    print("complete")

    task.cancel()

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

同期処理なので、バックグラウンドの処理は行われません。

$ python study1.py 
begin
complete

非同期タスクの場合

次は、非同期タスクの場合です。
task_background関数を追加している以外は、
docs.python.orgのサンプルとほぼ同じです。

study2.py

import asyncio

def blocking_io():
    with open('/dev/urandom', 'rb') as f:
        return f.read(100)

async def main():
    async def task_background():
        while True:
            print("background task")
            await asyncio.sleep(0)
    task = asyncio.create_task(task_background())

    loop = asyncio.get_running_loop()
    print("begin")
    result = await loop.run_in_executor(None, blocking_io)
    print("complete")

    task.cancel()

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

バックグラウンドの処理が動いていることが確認できます。

$ python study2.py
begin
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
complete

非同期タスクの場合(contextmanager版)

次は、contextmanagerを使った場合です。
async_funcという同期関数を非同期関数に置き換える関数を用意した上で、
async_openというcontextmanagerの中で、
openとreadを非同期関数に置き換えています。

python3.py

import asyncio
import functools
from typing import Callable
from contextlib import asynccontextmanager

def async_func(func) -> Callable:
    async def f(*args, **kwargs):
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(None, functools.partial(func, *args, **kwargs))
    return f

@asynccontextmanager
async def async_open(*args, **kwargs):
    fp = await async_func(open)(*args, **kwargs)
    fp.async_read = async_func(fp.read)
    try:
        yield fp
    finally:
        fp.close()

async def main():
    async def task_background():
        while True:
            print("background task")
            await asyncio.sleep(0)
    task = asyncio.create_task(task_background())

    loop = asyncio.get_running_loop()
    print("begin")
    async with async_open('/dev/urandom', 'rb') as f:
        print("begin read")
        result = await f.async_read(100)
        print("complete read")
    print("complete")

    task.cancel()

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

バックグラウンドの処理が動いていることが確認できます。 beginbegin readで、openが非同期に実行されていることがわかり、 begin readcomplete readで、readが非同期に実行されていることがわかります。

$ python study3.py
begin
background task
background task
begin read
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
background task
complete read
complete

標準ライブラリだけで、わりと楽に書けますね。

以上。