I have a queued PyTransition state machine, with some state doing work in on_enter. However, I want the user to be able to stop the machine at any time without waiting. For that, I need a way to cancel transitions.
Here is what I've found for now. However, accessing the _transition_queue_dict seems like a hack. Is there a proper way?
#!/usr/bin/env python3
import asyncio
import logging
from transitions.extensions.asyncio import AsyncMachine
logging.getLogger('transitions').setLevel(logging.DEBUG)
class Model:
async def do_long_work(self):
print("Working...")
await asyncio.sleep(10)
print("Worked!")
async def print_stop(self):
print("Stopped!")
async def interrupt(self):
global machine
await asyncio.sleep(1)
for task in machine.async_tasks[id(self)]:
task.cancel()
machine._transition_queue_dict[id(model)].clear()
await self.stop()
model = Model()
machine = AsyncMachine(model=model, queued=True)
machine.add_states('running', on_enter=[model.do_long_work])
machine.add_states('stopped', on_enter=[model.print_stop])
machine.add_transition('run', 'initial', 'running')
machine.add_transition('stop', 'running', 'stopped')
async def run():
await asyncio.gather(machine.dispatch('run'), model.interrupt())
asyncio.run(run())
I use the last commit on master (3836dc4).
The problem here is that you pass queued=True which instruct a machine to put new events into model queues and process events sequentially. Since you want to be able to interrupt events, omitting queued=True or setting queued=False (default value) will cancel events when you transition away/exit a state. No need to tinker with internal queues in this case.
import asyncio
import logging
from transitions.extensions.asyncio import AsyncMachine
logging.getLogger('transitions').setLevel(logging.DEBUG)
class Model:
async def do_long_work(self):
print("Working...")
await asyncio.sleep(10)
print("Worked!")
async def print_stop(self):
print("Stopped!")
async def interrupt(self):
await asyncio.sleep(1)
await self.stop()
model = Model()
machine = AsyncMachine(model=model, queued=False)
machine.add_states('running', on_enter=[model.do_long_work])
machine.add_states('stopped', on_enter=[model.print_stop])
machine.add_transition('run', 'initial', 'running')
machine.add_transition('stop', 'running', 'stopped')
async def run():
await asyncio.gather(machine.dispatch('run'), model.interrupt())
asyncio.run(run())
# Working...
# Stopped!
Related
For example this code:
async def f1(num):
while True:
print(num)
await asyncio.sleep(2)
class ExampleClass:
def __init__():
self.tasks = []
async def main():
for i in range(10):
tasks.append(asyncio.create_task(f1(i)))
await asyncio.gather(*tasks)
def add_new_task(task):
self.tasks.append(task)
Then somewhere outside I call
ExampleClass.add_new_task(task)
What I need is to add new tasks and execute them asynchronously with the existing ones.
May be I should use any other constructions to implement what i want?
What is important is that my tasks probably need to execute forever(forever polling)
The asyncio.gather function is not really convenient for such task. However, you can take a look at asyncio.TaskGroup which was released in Python3.11 and allows to add new tasks dynamically more easily.
import asyncio
from concurrent.futures import wait, FIRST_COMPLETED
async def main():
async with asyncio.TaskGroup() as group:
# Create some tasks
tasks = [
group.create_task(asyncio.sleep(1.0))
for _ in range(10)
]
# Wait for some tasks to finish
done, tasks = await wait(tasks, return_when=FIRST_COMPLETED)
# Add more tasks (/!\ `tasks` is now a set)
tasks.add(group.create_task(asyncio.sleep(2.0)))
# Wait the rest to complete
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())
So, I come to possible solution for my case with the idea of Louis.
The approximate idea is to use fake async linfinite loop where all my task are executed and where I update new tasks within asyncio.TaskGroup :
async def fake_generator():
while True:
yield None
fake_gen = fake_generator()
async with asyncio.TaskGroup() as tg:
async for _ in fake_gen:
await add_new_data()
for element in self.data:
if not self.data[element]['in_use']:
tg.create_task(job(element))
self.data[element]['in_use'] = True
await asyncio.sleep(60 * 60)
This question is best left as a small pseudo example:
async_queue = asyncio.queue
class SomeObject:
async def some_coroutine(self):
# does things...
def main():
instance = SomeObject()
async_queue.put(instance)
# In some other code, the `instance` will be get() from the queue
# and it's `some_coroutine` will be ran
# WAIT HERE NOW UNTIL `some_coroutine` in `instance`
# has completed... How do I do this?
As you can see, some_coroutine will be ran at some arbitrary point in the future and I want to wait for that coroutine to actually be ran and finished right after the point where I enqueued it.
How do I do this?
Figured it out about 15 minutes later. Something like this works:
import asyncio
class SomeObject:
def __init__(self):
loop = asyncio.get_running_loop()
self.completed = loop.create_future()
async def some_coroutine(self):
print('Processing things...')
await asyncio.sleep(3)
self.completed.set_result(True)
async def main():
instance = SomeObject()
# enqueue instance and pretend it's get()
# in some other code after 5 secs.
await asyncio.sleep(5)
await instance.some_coroutine()
await instance.completed
print("some_coroutine has finished")
asyncio.run(main())
I have a cog which is ment to handel events and loops. I am using aioschedule to run ping every minute as a test if the aioschedule works. But now the problem is that when I put it inside the class it asks for self but when i give self it gives an error.pls help with this
import discord
import os
import asyncio
import time
from discord.ext import commands,tasks
import aioschedule as schedule
class Events(commands.Cog):
def __init__(self, client):
self.client = client
async def bot_test_clear(self):
channel_bot_test = self.client.get_channel(os.getenv('bot-test-text'))
messages = await channel_bot_test.history(limit=100).flatten()
if not messages:
return
embed = discord.Embed(description='It has been 1 hour, clearing chats...', color=0xff0000)
await channel_bot_test.send(embed=embed)
await asyncio.sleep(10)
await channel_bot_test.purge(limit=None)
async def ping(self):
self.client.send("pong")
schedule.every(5).seconds.do(ping)
#commands.Cog.listener()
async def on_ready(self):
self.bot_text_clear.start()
print(f'{self.client.user} is now online')
def setup(client):
client.add_cog(Events(client))
loop = asyncio.get_event_loop()
while True:
loop.run_until_complete(schedule.run_pending())
time.sleep(0.1)
img of the error
So I answered your earlier question and suggested you use aioschedule so I will show you how I use it.
def _run_scheduler(self):
""" Create the scheduler task. """
async def _run_scheduler():
self._scheduler_running = True
print("Created 'Scheduler' task")
while self._scheduler_running:
await aioschedule.run_pending()
await asyncio.sleep(0.25)
if not self._scheduler_running:
asyncio.create_task(_run_scheduler())
The above method is attached to my bot, and is called once when the bot is created. The above code will process all the jobs you schedule. I use create_task so I can call the method from a non-async method.
#commands.Cog.listener()
async def on_startup(self):
aioschedule.every().monday.at("21:15").do(self.announce_winners)
aioschedule.every().day.at("21:30").do(self.start_quiz)
I add jobs, which are then processed in that event loop in the earlier code block. I use on_startup (a custom event) which is called after on_ready since on_ready can be called multiple times if the bot disconnects (stop duplicate jobs)
Your error (which should be text, not an image) is due to schedule.every(5).seconds.do(ping) being called and it is expecting self which you do not give it.
I come from the C++ world, and I'm looking for the equivalent of std::future, std::promise in Python. Is there an equivalent mechanism or another method in Python to achieve the same?
I'm aware of asyncio.Future, but I need it for threading not asyncio.
I'm using a third party library (PJSUA2) which I call directly from my main thread, but which send the results in asynchronous callbacks in context of a worker thread created by the library.
Expecting future/promise support in Python, I was hoping to write my application code like this:
future = wrap_foo(...)
if (future.get() != expected_result):
throw Exception(...)
future1 = wrap_foo(...)
future2 = wrap_bar(...)
I was planning on wrapping all library asynchronous calls with a wrap_xxx function (where the library function is called xxx) taking care of creating the future/promise objects.
I need the ability of having multiple futures pending, so I cannot simply make synchronous wrap_xxx functions which block until the result is ready.
See the asyncio module -
import asyncio
async def main():
print('hello')
await asyncio.sleep(1)
print('world')
asyncio.run(main())
hello
world
It supports coroutines -
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
started at 17:13:52
hello
world
finished at 17:13:55
And tasks -
import asyncio
async def nested():
return 42
async def main():
# Schedule nested() to run soon concurrently
# with "main()".
task = asyncio.create_task(nested())
# "task" can now be used to cancel "nested()", or
# can simply be awaited to wait until it is complete:
print(await task)
asyncio.run(main())
42
And Futures -
import asyncio
async def set_after(fut, delay, value):
# Sleep for *delay* seconds.
await asyncio.sleep(delay)
# Set *value* as a result of *fut* Future.
fut.set_result(value)
async def main():
# Get the current event loop.
loop = asyncio.get_running_loop()
# Create a new Future object.
fut = loop.create_future()
# Run "set_after()" coroutine in a parallel Task.
# We are using the low-level "loop.create_task()" API here because
# we already have a reference to the event loop at hand.
# Otherwise we could have just used "asyncio.create_task()".
loop.create_task(
set_after(fut, 1, '... world'))
print('hello ...')
# Wait until *fut* has a result (1 second) and print it.
print(await fut)
asyncio.run(main())
hello ...
... world
I'm working on a Discord bot with Python and it queues music from YouTube, i'm working on something to autoqueue songs when the player is stopped, while all of the code works perfectly, the only problem is me not being able to check if the player is playing or not every 15 seconds
async def cmd_autoqueue(self,message, player,channel,author, permissions, leftover_args):
print("autoq ran")
if started == True:
if player.is_stopped:
await self.cmd_autoqadd(player, channel, author, permissions,leftover_args,song_url=last_url)
threading.Timer(15.0,await self.cmd_autoqueue(message, player,channel,author, permissions, leftover_args)).start()
i did realise that
threading.Timer(15.0,await self.cmd_autoqueue(message, player,channel,author, permissions, leftover_args)).start()
calls the function, and if i wanted to pass it as something that would be called later i would use lambda: but , async lambda?
also started boolean is managed by other stuff so well its there for the sake of the 'if', here in this question
Solution:
async def cmd_autoqueue(self,message, player,channel,author, permissions, leftover_args):
global started
print("loop")
if started == True:
await asyncio.sleep(15)
if player.is_stopped:
await self.cmd_autoqadd(player, channel, author, permissions,leftover_args,song_url=last_url)
await self.cmd_autoqueue(message, player,channel,author, permissions, leftover_args)
You could use loop.call_later, that returns you an asyncio-compatible handler to cancel the task.
class YourClass:
def init(self):
self._loop = asyncio.get_event_loop()
self._check_handler = None
def schedule_check(self):
self._check_handler = loop.call_later(
15 * 60, # Every 15 minutes
self._loop.create_task, self._periodic_check())
def stop_checking(self):
self._check_handler.cancel()
async def start_player(self):
await do something()
if not check_handler:
schedule_check()
async def stop_player(self):
await do something()
self.stop_checking()
async def _periodic_check(self):
await do_something()
self._schedule_check()
To pass a function (that needs some parameters) as a parameter to another function, that doesn't allow you to pass the parameters, you can bind the parameters using functools.partial.