Ignition 8 invokeLater function doesn't seem to exist

Hi all,

Is there a problem with system.util.invokeLater? I keep getting this error even though my function runs fine with system.util.invokeAsynchronous. I was using 8.0.1 and then transferred over to an 8.0.2 rc from this past Friday.

I seem to be able to call it from the script console, and I feel like I have called it in vision before, but it fails in perspective (for reference, it is called on a property change script, but is located on a component’s script area).

Error message:

|SystemUtilities|20May2019 17:40:03|Error running function from system.util.invokeAsynchronous|
| --- | --- | --- |
|com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last): File "", line 47, in doAsynch AttributeError: 'com.inductiveautomation.ignition.common.script.Imm' object has no attribute 'invokeLater'
at org.python.core.Py.AttributeError(Py.java:207)
at org.python.core.PyObject.noAttributeError(PyObject.java:1032)
at org.python.core.PyObject.__getattr__(PyObject.java:1027)
at org.python.pycode._pyx32.doAsynch$2(:47)
at org.python.pycode._pyx32.call_function()
at org.python.core.PyTableCode.call(PyTableCode.java:171)
at org.python.core.PyBaseCode.call(PyBaseCode.java:308)
at org.python.core.PyFunction.function___call__(PyFunction.java:471)
at org.python.core.PyFunction.__call__(PyFunction.java:466)
at org.python.core.PyFunction.__call__(PyFunction.java:456)
at org.python.core.PyFunction.__call__(PyFunction.java:451)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:776)
at com.inductiveautomation.ignition.gateway.script.GatewaySystemUtilities.lambda$_invokeAsyncImpl$0(GatewaySystemUtilities.java:62)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.python.core.PyException: Traceback (most recent call last): File "", line 47, in doAsynch AttributeError: 'com.inductiveautomation.ignition.common.script.Imm' object has no attribute 'invokeLater'
... 14 common frames omitted|

Sample script:

    loadingLabel = self.parent.getChild("Options").getChild("Loading Label")
	loadingLabel.props.text = "Loading Table..."
	def doAsynch(site = site, deviceType = deviceType, loadingLabel = loadingLabel):
		devices = shared.device.getDevices(site, deviceType)
				
		deviceNames = devices["deviceNames"]
		devicePaths = devices["devicePaths"]
		
		deviceNames, devicePaths = shared.util.naturalSortLists(0, deviceNames, devicePaths)
		
		data = []
		headers = ["Device", "Device Path"]
		for index, devicePath in enumerate(devicePaths):
			deviceName = deviceNames[index]
			devicePath = devicePaths[index]
			tags = shared.device.getTags(devicePath)
			
			tagNames = tags["tagNames"]
			tagPaths = tags["tagPaths"]
			tagNames = [tagName for tagName in tagNames if "SCADA" not in tagName]
			tagPaths = [tagPath for tagPath in tagPaths if "SCADA" not in tagPath]
			
			for tagIndex in range(len(tagPaths)):
				tagPath = tagPaths[tagIndex]
				tagName = tagNames[tagIndex]
				if tagName not in headers:
					headers.append(tagName)
		
		data = []
		for index, devicePath in enumerate(devicePaths):
			data.append({})
			for headerIndex, header in enumerate(headers):
				if headerIndex == 0:
					data[index][header] = deviceNames[index]
				elif headerIndex == 1:
					data[index][header] = devicePaths[index]
				else:
					data[index][header] = None
		
		def doLater(component = self, loadingLabel = loadingLabel):
			loadingLabel.props.text = ""
			component.props.data = data
			#component.makeColumnAttributes()
			component.readData()
		system.util.invokeLater(doLater)
	
	system.util.invokeAsynchronous(doAsynch)

Any ideas? I don’t think it is scoping. Maybe something in my code is interfering with invokeLater? Or is this function removed?

Cheers,
Roger

1 Like

invokeLater is a concept that only makes sense in Vision - it posts a chunk of code to run later on the Event Dispatch Thread, which is a Java Swing thing.

I don’t know if there’s an analog you’re supposed to use in Perspective or not. Someone more familiar will have to answer tomorrow.

I’ve updated later.py for Ignition v8. It includes an implementation of invokeLater usable in gateway scope.

2 Likes

There isn’t any perspective analog for invokeLater that I’m aware of, but I’m struggling to think of a use-case in perspective that can’t be done some other way.

1 Like

This pattern isn’t necessary in Perspective; you can interact with the session or component from the other thread without the invokeLater part.

1 Like

This makes sense. So we should be able to just write back to UI components without needing invokeLater then?

The main reason i'm asking is because we had a template in vision that had a script with this combination and upon modifying it to work on a perspective view, the invokeLater part failed to function. If we don't need it, that makes life easier.

And indeed, removing it makes the script work as intended!

Cheers,
Roger

1 Like

You shouldn’t need system.util.invokeLater() in Perspective, but I would recommend a different approach to the doLater portion of the script: use system.perspective.sendMessage().

If you haven’t already encountered it, you’ll find that scripts like this which rely on hard-coded component paths will begin to fail as you iterate through development, because you’ll probably be renaming components and restructuring Views. Use of the sendMessage method will allow you to bypass script failures which would be caused by component paths no longer resolving. in your use-case, instead of the invokeLater() call, use

self.props.data = data
system.perspective.sendMessage(
    messageType='UPDATELOADINGLABEL',
    payload={'text':''},
    scope='view')

and then attach a message handler to the Loading Label component (set the Message Type to ‘UPDATELOADINGLABEL’, and make sure you set the scope to ‘view’):

self.props.text = payload['text']

You can also use this same call at the beginning of your script to set the initial text of the component to be “Loading Table…”.

2 Likes