I have a device which does file I/O over MIDI. I have a script using Mido that downloads files but it is a mess of global variables. I want to tidy it up to use asyncio properly but I am not sure how to integrate the mido callback. I think the documentation says I should use a Future object but I am not sure how the mido callback function can obtain that object.
Mido is a library for working with MIDI messages and ports: >>> import mido >>> msg = mido. Message('note_on', note=60) >>> msg.
Async IO takes long waiting periods in which functions would otherwise be blocking and allows other functions to run during that downtime. (A function that blocks effectively forbids others from running from the time that it starts until the time that it returns.)
An async function uses the await keyword to denote a coroutine. When using the await keyword, coroutines release the flow of control back to the event loop. To run a coroutine, we need to schedule it on the event loop. After scheduling, coroutines are wrapped in Tasks as a Future object.
asyncio is a library to write concurrent code using the async/await syntax. asyncio is used as a foundation for multiple Python asynchronous frameworks that provide high-performance network and web-servers, database connection libraries, distributed task queues, etc.
mido
provides a callback-based API which will invoke the callback from a different thread. Your implementation of the callback can communicate with asyncio by calling loop.call_soon_threadsafe
. Note that you won't be able to just set the value of a Future
because the callback will be called multiple times, and a future can only be set once - it is meant for one-shot computations.
A common pattern for multiply invoked callbacks is to push events onto an asyncio queue and pop stuff from it in asyncio code. This can be made even more convenient by exposing the queue as an async iterator. This function automates the process:
def make_stream():
loop = asyncio.get_event_loop()
queue = asyncio.Queue()
def callback(message):
loop.call_soon_threadsafe(queue.put_nowait, message)
async def stream():
while True:
yield await queue.get()
return callback, stream()
make_stream
returns two objects:
mido.open_input()
async for
to get new messagesWhenever the callback is invoked by mido in its background thread, your asyncio async for
loop iterating over the stream will wake up with a new item. Effectively, make_stream
converts a threaded callback into an async iterator. For example (untested):
async def print_messages():
# create a callback/stream pair and pass callback to mido
cb, stream = make_stream()
mido.open_input(callback=cb)
# print messages as they come just by reading from stream
async for message in stream:
print(message)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With