Run synchronous code#

Synchronous code will block the event loop and degrade the performance of the Quart application it is run in. This is because the synchronous code will block the task it is run in and in addition block the event loop. It is for this reason that synchronous code is best avoided, with asynchronous versions used in preference.

It is likely though that you will need to use a third party library that is synchronous as there is no asynchronous version to use in its place. In this situation it is common to run the synchronous code in a thread pool executor so that it doesn’t block the event loop and hence degrade the performance of the Quart application. This can be a bit tricky to do, so Quart provides some helpers to do so. Firstly any synchronous route will be run in an executor, i.e.

@app.route("/")
def sync():
    method = request.method
    ...

will result in the sync function being run in a thread. Note that you are still within the Contexts, and hence you can still access the request, current_app and other globals.

The following functionality accepts synchronous functions and will run them in a thread,

  • Route handlers

  • Endpoint handlers

  • Error handlers

  • Context processors

  • Before request

  • Before websocket

  • Before first request

  • Before serving

  • After request

  • After websocket

  • After serving

  • Teardown request

  • Teardown websocket

  • Teardown app context

  • Open session

  • Make null session

  • Save session

Context usage#

Whilst you can access the request and other globals in synchronous routes you will be unable to await coroutine functions. To work around this Quart provides run_sync() which can be used as so,

@app.route("/")
async def sync_within():
    data = await request.get_json()

    def sync_processor():
         # does something with data
         ...

    result = await run_sync(sync_processor)()
    return result

this is similar to utilising the asyncio run_in_executor function,

@app.route("/")
async def sync_within():
    data = await request.get_json()

    def sync_processor():
         # does something with data
         ...

    result = await asyncio.get_running_loop().run_in_executor(
        None, sync_processor
    )
    return result

Note

The run_in_executor function does not copy the current context, whereas the run_sync method does. It is for this reason that the latter is recommended. Without the copied context the request and other globals will not be accessible.