user10332687
user10332687

Reputation:

How to terminate an event loop

I have the following code in a django view to create a background task:

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.run_in_executor(None, update_contacts, {
        'email': email,
        'access_token': g.tokens['access_token']
    })

Is there anything I need to do at the end to 'kill' the loop? What would be the proper way to close it, etc?

Upvotes: 0

Views: 2723

Answers (2)

MisterMiyagi
MisterMiyagi

Reputation: 50096

You do not need to start any event loop in the first place. The concurrent.futures package gives direct access to Executors, and threading lets you launch individual Threads:

# raw thread
import threading

background_task = threading.Thread(
    target=update_contacts, kwargs={
        'email': email,
        'access_token': g.tokens['access_token']
})
background_task.start()

# executor thread pool
from concurrent.futures import ThreadPoolExecutor

my_executor = ThreadPoolExecutor()
my_executor.submit(update_contacts, email=email, access_token=g.tokens['access_token'])

In general, a Thread is simpler if you just want to launch a task and forget about it. A ThreadPoolExecutor is more efficient if you have many small tasks at the same time; it can also be used to automatically wait for completion of several tasks.

print('start at', time.time())
with ThreadPoolExecutor() as executor:
    executor.submit(time.sleep, 1)
    executor.submit(time.sleep, 1)
    executor.submit(time.sleep, 1)
    executor.submit(time.sleep, 1)
print('done at', time.time())  # triggers after all 4 sleeps have finished

The primary purpose of loop.run_in_executor is not to provide a ThreadPoolExecutor. It is meant to bridge the gap between Executors for blocking code and the event loop for non-blocking code. Without the later, there is no need to use asnycio at all.

import time
import asyncio

def block(delay: float):
    print("Stop! Blocking Time!")
    time.sleep(delay)  # block the current thread
    print("Done! Blocking Time!")

async def nonblock(delay: float):
    print("Erm.. Non-Blocking Time!")
    await asyncio.sleep(delay)
    print("Done! Non-Blocking Time!")

async def multiblock(delay: float):
    loop = asyncio.get_event_loop()
    await asyncio.gather(  # await async natively and sync via executors
        nonblock(delay),
        loop.run_in_executor(None, block, delay),
        nonblock(delay),
        loop.run_in_executor(None, block, delay),
    )

asyncio.run(multiblock(1))

Upvotes: 1

user4815162342
user4815162342

Reputation: 154916

Asyncio tasks can be canceled by calling the cancel method on the Task object. Tasks that run asynchronous code, such as those using the aiohttp library, will be canceled immediately. Tasks that run blocking code using run_in_executor will not be canceled because they are run in an OS thread behind the scenes.

This is part of the reason why run_in_executor is discouraged in asyncio code and is only intended as a stop-gap measure to include legacy blocking code in an asyncio program. (The other part is that the number of tasks is limited by the number of OS threads allowed by the pool, whereas the limit for the number of true asynchronous tasks is much higher.)

Upvotes: 0

Related Questions