Skip to content

Unblock

A common problem developers encounter when working with an asyncio event loop is long blocking code. This can be caused by a number of things, but the most common is a call to a library that is not async-aware, and has many blocking operations (such as requests, or even the built-in open() + read() functions).

To alleviate this, NioBot provides an "unblock" utility, which is a simple async function that will run any blocking code in the event loop executor, and returns the result, without pausing the event loop. This is equivalent to loop.run_in_executor(None, func, *args, **kwargs).

A good example

from niobot import NioBot, command
from niobot.utils import run_blocking

bot = NioBot(...)


@bot.command(name="read")
async def read_file(ctx: Context, filename: str):
    with open(filename, "r") as f:
        contents = await run_blocking(f.read)
    await ctx.respond(contents)

bot.run(...)
This will read the contents of a file, without blocking the event loop, unlike the following code:

A bad example

    from niobot import NioBot, command
    from niobot.utils import run_blocking

    bot = NioBot(...)


    @bot.command(name="read")
    async def read_file(ctx: Context, filename: str):
        with open(filename, "r") as f:
            contents = f.read()
        await ctx.respond(contents)

    bot.run(...)
This example is bad because it will prevent any other event processing until f.read() finishes, which is really bad if the file is large, or the disk is slow. For example, if you read at 1mb/s, and you have a 10 megabyte file, you will block the event loop for approximately 10 seconds, which means your program cannot do anything in those ten seconds, and as such your bot will appear to be non-functional!

run_blocking async

run_blocking(
    function: Callable[..., T], *args: Any, **kwargs: Any
) -> T

Takes a blocking function and runs it in a thread, returning the result.

You should use this for any long-running functions that may take a long time to respond that are not coroutines that you can await. For example, running a subprocess.

Example
import asyncio
import subprocess
from niobot.utils import run_blocking

async def main():
    result = await run_blocking(subprocess.run, ["find", "*.py", "-type", "f"], capture_output=True)
    print(result)

asyncio.run(main())

Parameters:

Name Type Description Default
function Callable[..., T]

The function to call. Make sure you do not call it, just pass it.

required
args Any

The arguments to pass to the function.

()
kwargs Any

The keyword arguments to pass to the function.

{}

Returns:

Type Description
T

The result of the function.

force_await async

force_await(
    function: Union[Callable, Coroutine],
    *args: Any,
    **kwargs: Any
)

Takes a function, and if it needs awaiting, it will be awaited. If it is a synchronous function, it runs it in the event loop, preventing it from blocking.

This is equivalent to (pseudo):

if can_await(x):
    await x
else:
    await run_blocking(x)

Parameters:

Name Type Description Default
function Union[Callable, Coroutine]

The function to call. Make sure you do not call it, just pass it.

required
args Any

The arguments to pass to the function.

()
kwargs Any

The keyword arguments to pass to the function.

{}

Returns:

Type Description

The result of the function.