Correct technique to deal with concurrency in async calls?

I have the following code. It is to do UPNP discovery. The problem is my async calls don't seem to update my state.devices variable properly. It's like the async calls are stomping on each other (race condition?). Maybe it's something else, I'm not sure, but what I can tell is my state variable isn't getting updated with all my device names even though each individual http request DOES return the device names. Any pointers?

void verifyDevices() {
    def devices = state.devices.findAll { it?.value?.verified != true }
    devices.each {
		def params = [
			uri: "http://${it.value.ip}:${it.value.port}",
			requestContentType: 'application/xml',
			contentType: 'application/xml',
			path: it.value.path
		]
		asynchttpGet(verifyDeviceCallback, params, [ip: it.value.ip])
    }
}

def verifyDeviceCallback(response, data) {
	def devices = state.devices
	def name = response.xml.device[0].friendlyName
	def ip = data.ip
	
	def device = devices.find { it?.key == ip }

	if (device) {
		device.value << [name: name, verified: true]
		state.devices[ip] = device.value
	}
	log.debug state.devices
}

Not that this is the right answer that you're looking for, but what happens if you add a delay between the calls? Do they behave like you would expect then?

Yeah. Pretty much. That’s what made me think it was concurrency. I switched to synchronous calls and it’s working. Not really what I wanted to do though.

That's what I ended up doing as well. Here's a thread I started when I was running into something similar. Maybe one of the suggestions in there will help you:

I have a an idea...

First, evaluate whether you need to use async at all? Can you run back-to-back synchronous calls in the background via a runIn call, while the UI page periodically refreshes waiting for the update to complete..

Here's an example... I would use atomicState since it's quicker to flush to the database. Create a flag like atomicState.upnpSearchActive and set it to true.. Kick of your background discovery... Have your page auto-refresh until atomicState.upnpSearchActive == false then display the results.

Here's some code in how I solved with with HubConnect 2.0.. I added a counter to create a progress bar to display to the user..

	if (atomicState?.drStatus == "start")
	{
		runIn(1, "generateDeviceReport")
		atomicState.drStatus = "starting"
		atomicState.drProgress = 0
	}
	if (atomicState?.drStatus == "starting" || atomicState?.drStatus == "running")
	{
		atomicState.drProgress++
		return waitPage("HubConnect is generating the device report; this may take several minutes...\n\nStatus: ${atomicState?.drStatus?.capitalize()} &nbsp;${"=".multiply(atomicState.drProgress)} &gt;")
	}

Your waitPage can be anything, just make sure you use a refreshInterval parameter with a short value (< 5 seconds) which determines the rate at which the page refreshes. When the results of the search are ready, the page loads.

Functionally, this displays a progress indication, then loads the results when the background tasks are done.. Here's a screenshot..

1 Like