Python Asyncio - running multiple infinite loops with different "pauses" - python

Im trying to figure out how to run multiple infinite loops with asyncio - each loop with it's own delays:
import asyncio
async def do_something(delay, message):
await asyncio.sleep(delay)
print(message)
def main():
loop = asyncio.get_event_loop()
loop.create_task(do_something(1, "delay equals 1"))
loop.create_task(do_something(3, "delay equals 3"))
loop.run_forever()
if __name__ == '__main__':
try:
main()
except Exception as f:
print('main error: ', f)
It returns:
delay equals 1
delay equals 3
and I would suspect it to return:
delay equals 1
delay equals 1
delay equals 1
delay equals 3
delay equals 1
delay equals 1
delay equals 3
(or similar)
How should I modify this simple routine?
SOLUTION
async def do_something(delay, message):
while True:
await asyncio.sleep(delay)
print(message)

There's no reason why a simple task would loop forever.
Depending on what you eventually want to do, you can add a while True: in the async functions, or have them schedule another task at the end.

Related

How to randomize tasks.loop timer in discord.py

i tried this:
timer = 15
random_timer = timer
channel = 108728307283
#tasks.loop(seconds = random_timer)
async def hello():
await channel.send("Hello!")
random_timer = timer + random.randint(1, 20)
hello.start()
My guess is that while a loop is running, it only looks at the value when it is run for the first time, so how do I set this random_timer value back to seconds without stopping the loop? In short, what should I do to send a message to a channel in a random time between 15-35 times?
To specify the total amount of iterations before exiting the loop, we can pass count to #tasks.loop()
#tasks.loop(seconds = random_timer, count=random.randint(15,35))
To change the interval timing of our loop during iteration, You can use function.change_interval()
#tasks.loop(seconds = random_timer, count=random.randint(15,35))
async def hello():
await channel.send("Hello!")
hello.change_interval(seconds=timer + random.randint(1, 20))

Issues with making a simple loading icon inside an async event loop

I'm making a bot for a discord server and have a function that takes a bit of time to run. I want to add a spinning loading icon next to the status message like this Doing something: <spinning icon>. It edits the original message to loop through these messages:
Doing something: \
Doing something: |
Doing something: /
Doing something: -
I tried using a separate thread to update the message like this:
async def loadingBar(ctx, message : discord.Message):
loadingMessage0 = "{0}: \\".format(message)
loadingMessage1 = "{0}: |".format(message)
loadingMessage2 = "{0}: /".format(message)
loadingMessage3 = "{0}: -".format(message)
index = 0
while True:
if(index == 0):
await message.edit(contents = loadingMessage0)
index = 1
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 2
elif(index == 2):
await message.edit(contents = loadingMessage2)
index = 3
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 0
farther down, the bot command that starts the process...
#bot.command()
async def downloadSong(ctx, url : str, songname : str):
#Other code that doesn't matter
message = await ctx.send("Downloading audio")
_thread = threading.Thread(target=asyncio.run, args=(loadingBar(ctx, message),))
_thread.start()
#Function that takes a while
#Some way to kill thread, never got this far
However, I get the error Task <Task pending coro=<loadingBar() running at bot.py:20> cb=[_run_until_complete_cb() at /Users/user/.pyenv/versions/3.7.3/lib/python3.7/asyncio/base_events.py:158]> got Future <Future pending> attached to a different loop. I'm new to async programming and the discord libraries; Is there a better way to do this and if not what am I doing wrong?
Firstly, you should add a delay between iterations inside the while loop, use asyncio.sleep for this.
Secondly - asyncio and threading doesn't really work together, there's also no point in using threading here since it defeats the whole purpose of asyncio, use asyncio.create_task to run the coroutine "in the background", you can asign it to a variable and then call the cancel method to stop the task.
import asyncio
async def loadingBar(ctx, message : discord.Message):
loadingMessage0 = "{0}: \\".format(message)
loadingMessage1 = "{0}: |".format(message)
loadingMessage2 = "{0}: /".format(message)
loadingMessage3 = "{0}: -".format(message)
index = 0
while True:
if(index == 0):
await message.edit(contents = loadingMessage0)
index = 1
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 2
elif(index == 2):
await message.edit(contents = loadingMessage2)
index = 3
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 0
await asyncio.sleep(1) # you can edit the message 5 times per 5 seconds
#bot.command()
async def downloadSong(ctx, url : str, songname : str):
message = await ctx.send("Downloading audio")
task = asyncio.create_task(loadingBar(ctx, message)) # starting the coroutine "in the background"
# Function that takes a while
task.cancel() # stopping the background task

`asyncio.run()` does not wait for coroutine to finish

I'm running this code in Python 3.7.3
import asyncio
async def fun(time):
print(f"will wait for {time}")
await asyncio.sleep(time)
print(f"done waiting for {time}")
async def async_cenas():
t1 = asyncio.create_task(fun(1))
print("after 1")
t2 = asyncio.create_task(fun(2))
print("after 2")
def main():
t1 = asyncio.run(async_cenas())
print("ok main")
print(t1)
if __name__ == '__main__':
main()
print("finished __name__")
And getting this output:
after 1
after 2
will wait for 1
will wait for 2
ok main
None
finished __name__
I was expecting to see also:
done waiting for 1
done waiting for 2
I.e., why was expecting asyncio.run(X) would wait for the coroutines to complete before proceeding.
If you want to wait for the completion of all tasks spawned by the create_task, then you need to do it explicitly by, for example, just await for them in turn or asyncio facilities like gather or wait (the difference is described here). Otherwise, they will be canceled by the asyncio.run when exiting the main coroutine, which is passed to asyncio.run.
Example:
import asyncio
async def fun(time):
print(f"will wait for {time}")
await asyncio.sleep(time)
print(f"done waiting for {time}")
async def async_cenas():
t1 = asyncio.create_task(fun(1))
print("after 1")
t2 = asyncio.create_task(fun(2))
print("after 2")
await asyncio.wait({t1, t2}, return_when=asyncio.ALL_COMPLETED)
# or just
# await t1
# await t2
def main():
t1 = asyncio.run(async_cenas())
print("ok main")
print(t1)
if __name__ == '__main__':
main()
print("finished __name__")
after 1
after 2
will wait for 1
will wait for 2
done waiting for 1
done waiting for 2
ok main
None
finished __name__

How to make a certain function stop executing when a certain condition is met

I am designing a discord bot that loops certain text when a certain conditions are met, which then triggers this function to run.
async def inform(self,message):
while flag==1:
await message.channel.send("text")
await asyncio.sleep(5)
await message.channel.send("text2")
await asyncio.sleep(5)
await message.channel.send("text3")
await asyncio.sleep(5)
Now the problem is when the conditions are not met anymore the function completes the entire cycle before haulting. I want it to stop the moment the condition is not satisfied anymore.
I thought of adding an
if flag==0:
return
After every line but that is not the elegant solution I am looking for.
I don't want the entire code to stop running, but just this function.
I am a beginner to python and any insights are welcome :)
Thank You!
A while loop, before each iteration will look if the condition is True, if it is, it will continue, if not it will stop.
while flag == 1:
if 'some condition met':
flag = 0
# And the loop will stop
The loop will stop by itself, nothing more. Also it's better to use booleans.
Here's your code a bit improved
# Defining variables
flag = True
iteration = 0
while flag:
# Adding `1` each iteration
iteration += 1
# Sending the message
await message.channel.send(f'text{iteration}')
# Sleeping
await asyncio.sleep(5)
If I got it right, You want to check if a condition is met. Here is an infinite loop until some criteria is met. If this is not what you wanted please explain it again.
import random
msgs = ['msg1','msg2','msg3']
condition_met = False
while True:
if condition_met:
break
# also return works here
else:
await message.channel.send(random.choise(msgs))
await asyncio.sleep(5)
if something == 'Condition met here':
condition_met = True
Each iteration of the loop should only send one message, to keep track of the order of messages sent, I used the variable i which is incremented every time a message is sent.
async def inform(self,message):
i = 0
while flag==1:
if i == 0:
await message.channel.send("text")
await asyncio.sleep(5)
elif i == 1:
await message.channel.send("text2")
await asyncio.sleep(5)
elif i == 2:
await message.channel.send("text3")
await asyncio.sleep(5)
i = (i + 1) % 3

How to set a background timer, and stop a function when it finishes while still waiting for reactions

So I'm making some minigame using discord.py, and this is what I got:
asyncio.create_task(self.stream_message_timer(ctx, correct, total), name=f"stream message timer {ctx.author.id}")
while bad_count > 0:
done, pending = await asyncio.wait([
self.client.wait_for('reaction_add', check=lambda reaction, user: str(reaction.emoji) in number_emojis and reaction.message.id == discord_message.id and user == ctx.author),
self.client.wait_for('message', check=lambda m: m.author == self.client.user and m.content == f"Times up!\n{correct}/{total} tasks successful.")
], return_when=asyncio.FIRST_COMPLETED)
try:
result = done.pop().result()
except Exception as e:
raise e
for future in done:
future.exception()
for future in pending:
future.cancel()
if type(result) == discord.Message:
return False
else:
reaction = result
# process the reaction, edit a message
await ctx.send(f"You deleted all the bad messages!\n{correct+1}/{total} tasks successful.")
for task in asyncio.all_tasks():
if task.get_name() == f"stream message timer {ctx.author.id}":
task.cancel()
break
return True
async def stream_message_timer(self, ctx, correct, total):
await asyncio.sleep(5)
await ctx.send(f"Times up!\n{correct}/{total} tasks successful.") # message linked to delete_chat, change there if this is changed
return False
Basically, I'm trying to make some kind of 5 second background timer as I wait for reactions at the same time.
No, I am not looking for timeout=5
The code that I have here works, but its very hacky. I'm either waiting for a reaction from the user, or just waiting for the bot to message "Times up".
Is there a cleaner way to do this? I would like to have the timer start right before the while loop, and forcefully stop anything inside the loop and make the function return False right when the timer finishes
Also, if the function does stop, I still want some way to cancel the timer, and that timer only.
I've been using this method for quite some time and it's making my code very unorganized. Thanks.
Here’s some sort of example that is independent from discord.py:
import asyncio
import random
async def main():
asyncio.create_task(timer(), name="some task name")
# simulates waiting for user input
await asyncio.sleep(random.uniform(2, 5))
return True
async def timer():
await asyncio.sleep(5)
# somehow make this return statement stop the main() function
return False
asyncio.run(main())
wait(return_when=FIRST_COMPLETED) is the correct way to do it, but you can simplify the invocation (and subsequent cancellation) by using the return value of create_task:
async def main():
timer_task = asyncio.create_task(timer())
user_input_task = asyncio.create_task(asyncio.sleep(random.uniform(2, 5)))
await asyncio.wait([timer_task, user_input_task], return_when=asyncio.FIRST_COMPLETED)
if not timer_task.done():
timer_task.cancel()
if user_input_task.done():
# we've finished the user input
result = await user_input_task
...
else:
# we've timed out
await timer_task # to propagate exceptions, if any
...
If this pattern repeats a lot in your code base, you can easily abstract it into a utility function.

Categories

Resources