python async - efficiency of sleeping multiple coroutines - python

Context: I am making a discord bot, and the mute command comes with a duration specifier for when the user should be unmuted again (this is being done via assigning a role to the user that removes their ability to send messages).
Idea 1: I could make a loop that checks every, say, 30 seconds and looks through to see which mutes have expired and cancel them.
Idea 2: Each time a mute is processed, I could await asyncio.sleep(however long) and then cancel it.
I would like to ask - which is more idiomatic, and also more importantly, which is more efficient? The first one has the advantage that it only has one coroutine running whereas the last one spawns a new one for each individual case (I don't really know how many that might be, but let's say around 10 concurrent cases maximum). However, the last one is easier for me to implement and feels like a more direct solution, on top of making the unmute happen exactly on time instead of on a timed loop.
There is one idea to make a loop that awaits until the next task and then when it processes that, queue up the next one, but then you can't insert tasks at the front of the queue.
TL;DR - if I need to schedule multiple events, do I run a loop to constantly check the scheduler, or do I open a coroutine for each and just await asyncio.sleep(however long) each of them?

Your second idea seems the most accurate and straightforward as you can simply use asyncio.create_task to run an unmute coroutine after a certain amount of time. By using asyncio.create_task, you do not need to return or yield back the unmute coroutine to be gathered later or submit it to be checked by a loop at an established interval, for awaiting the returned response from asyncio.create_task will run the task in the background:
import asyncio
async def unmute(delay, user_id):
await asyncio.sleep(delay)
#run unmute process
async def mute(user_id):
#your mute function
mute_for = await do_mute(user_id) #some mute function
u = asyncio.create_task(unmute(mute_for, user_id))
await u #submitting the task to be run in the background

Related

Why use async.gather in Python?

Let's say we have
await async_function_one_with_large_IO_request()
await async_function_two_with_large_IO_request()
versus
asyncio.gather(
async_function_one_with_large_IO_request(),
async_function_two_with_large_IO_request())
In the first version, once we hit the 'large io request' part of function one, it's gonna move onto running function_two, that's the whole point of await, right?
Isn't that what version 2 with gather does too?
What's the performance difference between the two?
In the first version, once we hit the 'large io request' part of function one, it's gonna move onto running function_two, that's the whole point of await, right?
That's incorrect. In your first version, async_function_two_with_large_IO_request (which I will call function_two) won't run until async_function_one_with_large_IO_request (which I will call function_one) completes.
If function_one happens to await on another function, it will yield control to another running async task, but function_two hasn't been scheduled yet.
When you use asyncio.gather, the tasks are scheduled concurrently, so if function_one awaits on something, function_two has a chance to run (along with other async tasks).
Note that asyncio.gather creates an async task, which generally implies you have to await on it:
await asyncio.gather(...)
The Python documentation covers this in detail in Coroutines and Tasks.

How do I run an infinitely looping coroutine in a different thread?

I'm trying to run an infinite loop to check a web server every 20 seconds, and if it finds something, I want it to send a message to a discord channel via my discord bot. However, I'm neither quite sure how asyncio works because I haven't used async/await much nor do I know how to actually implement this.
I've tried a few things:
async def poll():
...
await send()
threading.Thread(target = poll).start()
This fails because I don't await the poll function. If I don't include the async in async def poll that obviously fails because then the await send() isn't valid.
async def poll():
...
await send()
asyncio.run_coroutine_threadsafe(poll(), asyncio.get_event_loop()) # this freezes the rest of my program and prevents my discord bot from working correctly
asyncio.run_coroutine_threadsafe(poll(), asyncio.new_event_loop()) # this warns me saying "coroutine poll was never awaited" and doesn't seem to execute the loop
I doubt I'm supposed to use threads with asyncio. But how would I make an infinite loop run in parallel to the rest of my code?
If you want this for a discord bot and using discord.py, then you can use discord.ext.tasks.loop or a background task.
Loop Example: Here
Background Task Example: Here
None of this will affect your bot, until you are not using a blocking module like requests or time.sleep()

How to have something similar to Javascript callbacks in Python

I am trying to get my head around Python asyncio . This is a simple program i wrote . The logic i am trying to simulate is as follows:
I get a list of names from some database. Since we are going to do something with those names after we get them hence i made it a simple function and not an asynchronous one.
After we get the data we again make a call to some external API using the name that we have. Now since this would be an expensive operation from IO standpoint and the API calls for individual names don't depend on each other it makes sense to make them anonymous.
I looked up this thread in Stackoverflow(Cooperative yield in asyncio) which says that to give back control to the event loop to do something else we have to do asyncio.sleep(0).
Here i am comparing the async behaviour of Node.js and Python. If i give back control to the event loop using the above syntax my long running API call would remain suspended right and would not happen in the background as in Node.js?
In Node.js when we make an external API call we get something back called Promises on which we can wait to finish . It essentially means that the database call or API call is happening in the background and we get back something when it is done.
Am i missing something critical concept here about Python asynchronous programming ? Kindly throw some more light on this .
Below is the code and its output:
import asyncio
import time
async def get_message_from_api(name):
# This is supposed to be a long running operation like getting data from external API
print(f"Attempting to yield control to the other tasks....for {name}")
await asyncio.sleep(0)
time.sleep(2)
return f"Creating message for {name}"
async def simulate_long_ops(name):
print(f"Long running operation starting for {name}")
message = await get_message_from_api(name)
print(f"The message returned by the long running operation is {message}")
def get_data_from_database():
return ["John", "Mary", "Sansa", "Tyrion"]
async def main():
names = get_data_from_database()
futures = []
for name in names:
futures.append(loop.create_task(simulate_long_ops(name)))
await asyncio.wait(futures)
if __name__ == '__main__':
try:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
except Exception as e:
print(e)
finally:
loop.close()
Output:
Long running operation starting for John
Attempting to yield control to the other tasks....for John
Long running operation starting for Mary
Attempting to yield control to the other tasks....for Mary
Long running operation starting for Sansa
Attempting to yield control to the other tasks....for Sansa
Long running operation starting for Tyrion
Attempting to yield control to the other tasks....for Tyrion
The message returned by the long running operation is Creating message for John
The message returned by the long running operation is Creating message for Mary
The message returned by the long running operation is Creating message for Sansa
The message returned by the long running operation is Creating message for Tyrion
The mistake in your code is that you call time.sleep. You should never call that function in asyncio code, it blocks the whole event loop; await asyncio.sleep() instead. In JavaScript terms, calling time.sleep is almost as bad as sleeping like this instead of like this. (I say "almost" because time.sleep at least doesn't burn CPU cycles while waiting.)
Attempts to work around that mistake led to the second problem, the use of asyncio.sleep(0) to give control to the event loop. Although the idiom was added early, the behavior was documented only much later. As Guido hints in the original issue, explicit yielding to the event loop is only appropriate for advanced usage and its use by beginners is most likely an error. If your long-running operation is async ― as is the case in your code, once time.sleep() is replaced with await asyncio.sleep() ― you don't need to drop to the event loop manually. Instead, the async operation will drop as needed on every await, just like it would in JavaScript.
In Node.js when we make an external API call we get something back called Promises on which we can wait to finish.
In Python a future is a close counterpart, and the async models are very similar. One significant difference is that Python's async functions don't return scheduled futures, but lightweight coroutine objects which you must either await or pass to asyncio.create_task() to get them to run. Since your code does the latter, it looks correct.
The return value of create_task is an object that implements the Future interface. Future sports an add_done_callback method with the semantics you'd expect. But it's much better to simply await the future instead - it makes the code more readable and it's clear where the exceptions go.
Also, you probably want to use asyncio.gather() rather than asyncio.wait() to ensure that exceptions do not go unnoticed. If you are using Python 3.7, consider using asyncio.run() to run the async main function.

Multi-threading with Discord (scheduled job)

Firstly, an apology. I'm pretty new to Python. I come from a Java/C# coding background. I'm loving Pythons simplicity in many ways, but also finding some standards hard to pin down.
For instance, I've successfully managed to get a Discord Bot running. The async methods are working well. But I would like to schedule a job to run every (say) 30 minutes. However, when I type asyncio.run(job()), Python tells me that "run" is not an attribute of asyncio. I'm really not sure why it would say that. Heck, is asyncio even the "right" way to do this?
Is it possible the discord import has masked it in some way? I think I might need to get a book or something!
Again, thanks. I did try a search on this, but nothing came up!
on_ready is called when the discord bot gets initiated so one way is to attach your job to it:
import discord
import asyncio
client = discord.Client()
#client.event
async def on_ready():
while True:
await asyncio.sleep(30*60) # every 30 minutes
job()
client.run(os.environ.get('DISCORD_BOT_SECRET')) # provide your API token here!!
asyncio.sleep is a non-blocking sleep -- if one were to use time.sleep here, then the bot would wait for time.sleep to complete and would be unresponsive to any other messages coming in. But what await asyncio.sleep does is yield control back to the event loop which can take care of other bot functions. Only after 30 minutes control will return to on_ready.
Note that while your job runs it will block your bot which is a problem for jobs tasking longer than a few seconds. If your job is I/O-based (e.g. fetching websites) you can use async I/O operations (such as aiohttp) to keep it responsive. If your job is CPU-based you might have to use multiple processes, such as subprocess.Popen if your job can be invoked with a terminal command.
The modern way to schedule a job every 30 minutes is using discord.ext.tasks:
import asyncio
import contextlib
import discord
from discord.ext import tasks
client = discord.Client()
#tasks.loop(minutes=30) # every 30 minutes
async def job():
with contextlib.suppress(Exception):
... # your code here
job.start()
client.run(os.environ.get('DISCORD_BOT_SECRET')) # provide your API token here!!
The function job() will be called every 30 minutes by the Discord client's event loop. A couple notes here:
If job() throws an exception, the ENTIRE LOOP will stop. That is right, not just this invocation, but the entire loop will exit. That is why I added a with-statement to catch all exceptions, this is equivalent to try: ... except: pass.
While job() is running, the other parts of your bot are blocked. This is because async is not implemented using multiple threads but using coroutines in a single thread. A way around this could be to make your code async (e.g. using aiohttp for web requests) or to start another process using subprocess.Popen.

Python asyncio: how single thread can handle mutiple things simultaneously?

Hi I am new to asyncio and concept of event loops (Non-blocking IO)
async def subWorker():
...
async def firstWorker():
await subWorker()
async def secondWorker():
await asyncio.sleep(1)
loop = asyncio.get_event_loop()
asyncio.ensure_future(firstWorker())
asyncio.ensure_future(secondWorker())
loop.run_forever()
here, when code starts, firstWorker() is executed and paused until it encounters await subWorker(). While firstWorker() is waiting, secondWorker() gets started.
Question is, when firstWorker() encounters await subWorker() and gets paused, the computer then will execute subWorker() and secondWorker() at the same time. Since the program has only 1 thread now, and I guess the single thread does secondWorker() work. Then who executes subWorker()? If single thread can only do 1 thing at a time, who else does the other jobs?
The assumption that subWorker and secondWorker execute at the same time is false.
The fact that secondWorker simply sleeps means that the available time will be spent in subWorker.
asyncio by definition is single-threaded; see the documentation:
This module provides infrastructure for writing single-threaded concurrent code
The event loop executes a task at a time, switching for example when one task is blocked while waiting for I/O, or, as here, voluntarily sleeping.
This is a little old now, but I've found the visualization from the gevent docs (about 1 screen down, beneath "Synchronous & Asynchronous Execution") to be helpful while teaching asynchronous flow control to coworkers: http://sdiehl.github.io/gevent-tutorial/
The most important point here is that only one coroutine is running at any one time, even though many may be in process.

Categories