Executing a function after a certain period of time - python

I am building a discord bot with discordpy and I want a function to be executed every ten minutes (for a mini game) but if I use time.sleep the entire program will freeze and wait for that time, rendering my bot completely useless because of the fact that time.sleep stops the program from executing. Also discordpy works with async functions and events so trying to find a place to put a while loop is very difficult. Is there a module that I can use to execute a function every ten minutes without stopping the flow of my bot?
edit:
with discordpy, you define all of your async functions so:
#client.event
async def on_message(message):
# Code
And than at the end of the file you write:
client.run()
What I am saying is, I cant use an infinite while loop because of the fact that I need to reach that line, without that line the bot will be useless, So what my question is, can I "attach" a timer to my script so that every ten minutes I can execute a function?

you use scheduling for this
import sched, time
sch = sched.scheduler(time.time, time.sleep)
def run_sch(do):
print("running at 10 mins")
# do your stuff
sch.enter(600, 1, run_sch, (do,))
sch.enter(600, 1, run_sch, (s,))
sch.run()
or you can try threading for running that specific function for every 10 mins
import threading
def hello_world():
while True:
print("Hello, World!")
time.sleep(600)
t1 = threading.Thread(target=hello_world)
t1.start()
while True:
print('in loop')
time.sleep(1)

Tell me what you think about this. If it does not work with your code then I can adjust it:
import time
starttime=time.time()
def thing():
print('hi')
while True:
thing()
time.sleep(60.0 - ((time.time() - starttime) % 60.0))
It's in a while loop so I don't know how well that will work with your code but since it is a bot that runs multiple times, it might work. Of course if you want it to run only 5 times for example you can just say for i in range(5):
Hope this helps!

Try something like this,
import schedule
def worker():
print("Executing...")
schedule.every(10).minutes.do(worker)
while True:
schedule.run_pending()
time.sleep(1)
Also, there are different packages we can use to achieve this feature.
import sched
sched.scheduler(params)
Threading along with sleep.
Use of Twisted package etc..

Discord.py is build with python asyncio module. To sleep the current task in asyncio, you need to await on asyncio.sleep() Note that this will not block your thread or the event loop and is exactly what you should use.
import asyncio #Top of your file
...
await asyncio.sleep(10) #Doesn't blocks the rest of the program!
Creating a thread as mentioned in the answer by NAGA RAJ S is not recommended in asynchronous programs and is a waste of resource.
More about asyncio.sleep v/s time.sleep: Python 3.7 - asyncio.sleep() and time.sleep()

Related

How can I make a recurring async task (I don't control where asyncio.run() is called)

I'm using a library that itself makes the call to asyncio.run(internal_function) so I can't control that at all. I do however have access to the event loop, it's something that I pass into this library.
Given that, is there some way I can set up an recurring async event that will execute every X seconds while the main library is running.
This doesn't exactly work, but maybe it's close?
import asyncio
from third_party import run
loop = asyncio.new_event_loop()
async def periodic():
while True:
print("doing a thing...")
await asyncio.sleep(30)
loop.create_task(periodic())
run(loop) # internally this will call asyncio.run() using the given loop
The problem here of course is that the task I've created is never awaited. But I can't just await it, because that would block.
Edit: Here's a working example of what I'm facing. When you run this code you will only ever see "third party code executing" and never see "doing my stuff...".
import asyncio
# I don't know how the loop argument is used
# by the third party's run() function,
def third_party_run(loop):
async def runner():
while True:
print("third party code executing")
await asyncio.sleep(5)
# but I do know that this third party eventually runs code
# that looks **exactly** like this.
try:
asyncio.run(runner())
except KeyboardInterrupt:
return
loop = asyncio.new_event_loop()
async def periodic():
while True:
print("doing my stuff...")
await asyncio.sleep(1)
loop.create_task(periodic())
third_party_run(loop)
If you run the above code you get:
third party code executing
third party code executing
third party code executing
^CTask was destroyed but it is pending!
task: <Task pending name='Task-1' coro=<periodic() running at example.py:22>>
/usr/local/Cellar/python#3.10/3.10.8/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/base_events.py:674: RuntimeWarning: coroutine 'periodic' was never awaited
You don't need to await on a created task.
It will run in the background as long as the event loop is active and is not stuck in a CPU bound operation.
According to your comment, you don't have an access to the event loop. In this case you don't have many options other than running in a different thread (which will have its own loop), or changing the loop creation policy in order to get the event loop, which is a very bad idea in most cases.
I found a way to make your test program run. However, it's a hack. It could fail, depending on the internal design of your third party library. From the information you provided, the library has been structured to be a black box. You can't interact with the event loop or schedule a callback. It seems like there might be a very good reason for this.
If I were you I would try to contact the library designer and let him know what your problem is. Perhaps there is a better solution. If this is a commercial project, I would make 100% certain that the team understands the issue, before attempting to use my below solution or anything like it.
The script below overrides one method (new_event_loop) in the DefaultEventLoopPolicy. When this method is called, I create a task in this loop to execute your periodic function. I don't know how often, or for what purpose, the library will call this function. Also, if the library internally overrides the EventLoopPolicy then this solution will not work. In both of these cases it may lead to unforeseeable consequences.
OK, enough disclaimers.
The only significant change to your test script was to replace the infinite loop in runner with a one that times out. This allowed me to verify that the program shuts down cleanly.
import asyncio
# I don't know how the loop argument is used
# by the third party's run() function,
def third_party_run():
async def runner():
for _ in range(4):
print("third party code executing")
await asyncio.sleep(5)
# but I do know that this third party eventually runs code
# that looks **exactly** like this.
try:
asyncio.run(runner())
except KeyboardInterrupt:
return
async def periodic():
while True:
print("doing my stuff...")
await asyncio.sleep(1)
class EventLoopPolicyHack(asyncio.DefaultEventLoopPolicy):
def __init__(self):
self.__running = None
super().__init__()
def new_event_loop(self):
# Override to create our periodic task in the new loop
# Get a loop from the superclass.
# This method must return that loop.
print("New event loop")
loop = super().new_event_loop()
if self.__running is not None:
self.__running.cancel() # I have no way to test this idea
self.__running = loop.create_task(periodic())
return loop
asyncio.set_event_loop_policy(EventLoopPolicyHack())
third_party_run()

Is this a good alternative of asyncio.sleep

I decided not use asyncio.sleep() and tried to create my own coroutine function as shown below. Since, time.sleep is an IO bound function, I thought this will print 7 seconds. But it prints 11 seconds.
import time
import asyncio
async def my_sleep(delay):
time.sleep(delay)
async def main():
start = time.time()
await asyncio.gather(my_sleep(4), my_sleep(7))
print("Took", time.time()-start, "seconds")
asyncio.run(main())
# Expected: Took 7 seconds
# Got: Took 11.011508464813232 seconds
Though if I write a similar code with threads, It does print 7 seconds. Do Task objects created by asyncio.gather not recognize time.sleep as an IO bound operation, the way threads do? Please explain why is it happening.
time.sleep is blocking operation for event loop. It has no sense if you write async in defention of function because it not unlock the event loop (no await command)
This two questions might help you to understand more:
Python 3.7 - asyncio.sleep() and time.sleep()
Run blocking and unblocking tasks together with asyncio
This would not work for you because time.sleep is a synchronous function.
From the 'perspective' of the event loop my_sleep might as well be doing a heavy computation within an async function, never yielding the execution context while working.
The first tell tale sign of this is that you're not using an await statement when calling time.sleep.
Making a synchronous function behave as an async one is not trivial, but the common approach is moving the function call to worker threads and awaiting the results.
I'd recommend looking at the solution of anyio, they implemented a run_sync function which does exactly that.

Efficient way to wait in python without using input()?

I have a python script where I use a listener from another library to wait and listen for an event to occur (that my script then handles). In development, I used an input() statement (inside a while True loop) at the end of my script to efficiently keep the script alive while doing nothing (other than waiting for the event). However, now that I've put this into a systemd service, the input() fails with an EOF since system services are not expected to have any console IO. What is a 'nice' or pythonic way to achieve essentially an endless loop here? I could do a while True: pass or while True: sleep(0.1) but the first burns the CPU, while the second seems hackish.
Have a look at official document about coroutines.
Example:
import time, asyncio
async def run_task():
for i in range(5):
print('running task %d' % i)
await awaiting_task(i)
async def awaiting_task(name):
time.sleep(5) # wait for 5 seconds
print('task %s finished' % str(name))
asyncio.run(run_task())
The async syntax will turn the function to a coroutine, which saves your cpu if possible, other than busy waiting.

How to schedule different tasks at different times in never-ending program

I'll preface this by saying I'm not an advanced programmer and I have only written programs that run sequentially and exit. What I'd like to do now is write a python script that I'll launch and it will run a function every 5 minutes and another function every 10 minutes and do so indefinitely. Here's some pseudo-code:
def RunMeEvery5min:
do something
def RunMeEvery10min:
do something
while True:
every 5 minutes run RunMeEvery5min
every 10 minutes run RunMeEvery10min
do this forever until I kill the program
So is this threading? It really doesn't matter if the tasks line up or not as they're essentially unrelated. I would venture to guess that this is a common type of programming question, but I've never really understood how to accomplish this and I don't even know what to search for. Any helpful examples or links to basic tutorials would be much appreciated!
Thanks!
Maybe this will help you https://github.com/dbader/schedule
import schedule
import time
def job():
print("I'm working...")
schedule.every(10).minutes.do(job)
while True:
schedule.run_pending()
time.sleep(1)
You can use sched from Python standard library.
import sched, time
from datetime import datetime
scheduler = sched.scheduler(time.time, time.sleep)
def execute_every_05mins():
print(datetime.now().strftime("%H:%M:%S"))
scheduler.enter(300, 0, execute_every_05mins, ())
def execute_every_10mins():
print(datetime.now().strftime("%H:%M:%S"))
scheduler.enter(600, 0, execute_every_10mins, ())
if __name__ == "__main__":
scheduler.enter(0, 0, execute_every_05mins, ())
scheduler.enter(0, 0, execute_every_10mins, ())
scheduler.run()

Why does this Python async script never finish?

I created a MCVE example of a much larger code base that I am working on, so some things look to be a funky way of doing things, but they are just the minimal versions, so not everything makes sense as far as WHY I am doing it this way. I already know some work-arounds, and at this point am mostly just curious as to why I am seeing this behaviour.
I have the following script which has an async function that waits for a future. The script then intercepts a signal to set the future result:
import asyncio
import time
import signal
f = asyncio.Future()
loop = asyncio.get_event_loop()
async def wait_till_signal():
await f
def send_stop():
print('stopping', time.time())
f.set_result(True)
print('sent')
print(f.result())
def handle_signal(s, a):
print('sending stop', time.time())
send_stop()
signal.signal(signal.SIGINT, handle_signal)
loop.run_until_complete(wait_till_signal())
This script correctly gets the interrupt, and appears to set the future correctly, but for some reason the script never terminates.
To reproduce for you, just run the script, then hit ctrl+c. For some reason it never stops.
Now here is where it gets weird. If you add the following to the top of the script (after defining the loop), then the script stops just fine.
async def do_nothing_useful():
for i in range(30):
await asyncio.sleep(1)
loop.create_task(do_nothing_useful())
Why is the coroutine not getting the future in the first case, but it gets it correctly in the second case?
Also, another weird thing is that if you set the send_stop function to be async, and add it as a task, it never gets called. (This follows the same behaviour as above. If the do_nothing_useful() function is on the loop, everything works fine, but without it, it doesnt)
Here is the version where the send_stop is never called:
import asyncio
import time
import signal
f = asyncio.Future()
async def wait_till_signal():
await f
async def send_stop():
# this is async only because we are trying to try out crazy things
print('stopping', time.time())
f.set_result(True)
print('sent')
print(f.result())
def handle_signal(s, a):
print('sending stop', time.time())
loop.create_task(send_stop())
signal.signal(signal.SIGINT, handle_signal)
loop = asyncio.get_event_loop()
loop.run_until_complete(wait_till_signal())
print('done')
and the script never prints stopping.
I have tried this on python 3.5.3 and 3.6 on linux
The correct method to add a signal handler that wakes up a loop is by using loop.add_signal_handler. This will make sure select() wakes up to handle the signal.

Categories