4

asyncio: Can I wrap a sync REST call in async? FTX fetch_position is a REST API call, it's not async and not awaitable. I tried below hoping if each call is 300ms, total not 300ms x3 = 900ms, but rather (wishful thinking) 300ms for all three using asyncio magic (Coorperative multi-tasking). But it didn't work. Overall took about 900ms. Am i doing something wrong here? Thanks!

async def _wrapper_fetch_position(exchange : ccxt.Exchange):
                pos = exchange.fetch_positions()
                return pos

import asyncio
import nest_asyncio
loop = asyncio.get_event_loop()
nest_asyncio.apply()
pos1 = loop.run_until_complete(_wrapper_fetch_position(exchange1))
pos2 = loop.run_until_complete(_wrapper_fetch_position(exchange2))
pos3 = loop.run_until_complete(_wrapper_fetch_position(exchange3))

1 Answer 1

1

Directly, no, you'll block the event loop and won't get concurrency. However, you can use multithreading with asyncio, wrapping sync calls in a thread with asyncio's to_thread coroutine. This delegates a blocking function to run in a separate thread backed by a ThreadPoolExecutor and returns an awaitable, so you can use it in await expressions just like it was non-blocking. Here is an example with the requests library to make 20 web requests, comparing synchronous with threads:

import asyncio
import time
import requests


def in_sequence():
    for i in range(20):
        requests.get('https://www.example.com')


async def with_threads():
    def make_request(): requests.get('https://www.example.com')
    reqs = [asyncio.to_thread(make_request) for _ in range(20)]
    await asyncio.gather(*reqs)


async def main():
    sequence_start = time.time()
    in_sequence()
    sequence_end = time.time()
    print(f'In sequence {sequence_end - sequence_start}')

    thread_start = time.time()
    await with_threads()
    thread_end = time.time()
    print(f'With threads {thread_end - thread_start}')


asyncio.run(main())

Running this on my machine, I get the following results, demonstrating the performance difference:

In sequence 1.9963197708129883
With threads 0.26117658615112305

If you want more control over the thread pool, you can manually create one and use asyncio's loop.run_in_executor method. See https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor for more details and an example on how to use this.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you Matt. But i am really confused about run_in_executor or run_in_loop: What's their purpose if there's no concurrency?
@user3761555 there is concurrency, you're just achieving it by using threads with these methods.
Got it, you need use asyncio.gather with run_in_executor: results = await asyncio.gather(*[one_loop.run_in_executor(None, _sync_parrot, 1, "whack whack") for _ in range(NUM_REQUESTS)])

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.