KDMcCracken
KDMcCracken

Reputation: 17

Utilizing asyncio to loop through a list and make callouts without waiting for the respose of the previous call

async def load_devices(deviceName):
    deviceIP = deviceName[1]
    deviceNumber = deviceName[2]
    device = BAC0.device(deviceIP, deviceNumber, bacnet, poll=trends_every)
    return device  

def get_attributes(self):
    self.get_attribute_button.destroy()

    deviceNames = list(bacnet.devices.values.tolist())

    loop = asyncio.get_event_loop()

    global devices

    for device in deviceNames:
        devices = loop.run_until_complete(asyncio.gather(load_devices(device)))
    loop.close()

I have done a lot of surfing for the solution to this and none of the applications I have found work when applied to my situation. The BAC0.device() callout is what my code is constantly waiting for. What I have now does not move onto the next device callout until the previous one has returned and this is significantly slowing performance.

What I need to do is create those devices asynchronously so that it is not constantly waiting for the previous one to connect before making the next one. Thanks for any advice/help!

Upvotes: 0

Views: 605

Answers (2)

Christian Tremblay
Christian Tremblay

Reputation: 839

Worth noting that every call to a BAC0.device depends on a thread that bacpypes (the module that handle BACnet communication) creates when we create the bacnet network. And this thread makes all the network calls to be executed one after the other.

Bacpypes (because of the way BACnet works) will keep the socket open as long as it runs so you will not be able to launch multiple bacnet instances running at the same time... you would need more than one NIC (or any other trick that would allow opening a new socket) to be able to make that asynchronous.

If you want to gain speed, I think you would have better results defining all the devices globally, and use custom object lists to reduce the number of points.

Once defined, all the devices will get updated in the background. You can then use the

dev[“MyPoint”].lastValue

Trick to force your soft to use the last polled value (instead of forcing a new read on the network)

If you really want to use async and await for BACnet communication, you would probably need to start with bacpypes itself. I know that Joel is thinking about a way to make the module asynchronous but it’s still far away. But BAC0 being nothing more than a “wrapper” around bacpypes, it is really “synchronous”.

If BAC0 is to be used inside an asynchronous app, I think there are some “ways” to handle synchronous stuff but here ends my knowledge about async and await.

By the way, if you make some progress with it, don’t hesitate to drop by Github and leave something ! I’m really interested in knowing what is done with BAC0.

Upvotes: 1

Draconis
Draconis

Reputation: 3461

Right now, your load_devices is asynchronous, but you're waiting for each call to it to finish before starting the next one.

I'd replace your final for loop with this.

tasks = [load_devices(device) for device in deviceNames]
await asyncio.gather(*tasks)

This makes an awaitable for each call, then awaits them all at once, rather than awaiting each individually.

(Note: the tasks list technically contains coroutines, not Task objects, because asyncio.gather does all the Task-creation automatically. You can run asyncio.create_task on each one if you really want to, but it won't change the outcome.)

EDIT: It sounds like you want to do this in a non-async function. In that case:

tasks = [load_devices(device) for device in deviceNames]
devices = loop.run_until_complete(asyncio.gather(*tasks))

Upvotes: 0

Related Questions