TrentR
TrentR

Reputation: 43

How to feed an async generator with events from python.net

I am using a third party .Net library in Python 3.6 via Python for .Net which uses EventHandlers to provide asynchronous data to my application, similar to the toy example below:

import clr
from System import Timers

def tock(__, args):
    print(args.SignalTime)

timer = Timers.Timer()
timer.Interval = 1000
timer.Elapsed += tock
timer.Enabled = True
while True:
    pass

I would like to get this data into an asynchronous generator, something like:

import clr
from System import Timers

async def tock(__, args):
    yield args.SignalTime

async def main():
    result = await tock
    print(result)

timer = Timers.Timer()
timer.Interval = 1000
timer.Elapsed += tock
timer.Enabled = True
while true:
    result = await timer
    print result

Obiviously just throwing asyc and yield on the event handler function and awaiting the timer won't do it, but is there a straightforward means of accomplishing this?

Upvotes: 4

Views: 1180

Answers (1)

user4815162342
user4815162342

Reputation: 154996

Since the "asynchronous" data comes from another thread, you will need a bridge between asyncio and the thread that invokes tock. On the asyncio side, you will need to implement an async generator and iterate over it with an async for loop. For example (untested):

import clr, asyncio
from System import Timers

def adapt_sync_to_async():
    # Adapt a series of sync callback invocations to an async
    # iterator. Returns an async iterator and a feed callback
    # such that the async iterator will produce a new item
    # whenever the feed callback is fed one.
    loop = asyncio.get_event_loop()
    queue = asyncio.Queue()
    def feed(item):
        loop.call_soon_threadsafe(queue.put_nowait, item)
    async def drain():
        while True:
            yield await queue.get()
    return drain, feed

tock, feed_tock = adapt_sync_to_async()

async def main():
    async for result in tock():
        print(result)

timer = Timers.Timer()
timer.Interval = 1000
timer.Elapsed += lambda _, args: feed_tock(args.SignalTime)
timer.Enabled = True

asyncio.get_event_loop().run_until_complete(main())

Upvotes: 4

Related Questions