Hello there!
I was testing the system.util.sendRequestAsync on the perspective view.
I have a simple task: the user clicks the "Refresh" button, and it sends a request to the gateway to execute a heavy task and update the database in the end. After that, I need to update the table component by refreshing the binding. Sounds easy, and I think many users get the same task.
I want to make it based in sendRequestAsync message and use onSuccess to update the bindings.
I wrote a simple script just to try sendRequestAsync:
Button script
def handleMessage(payload):
logger = system.util.getLogger("TestHandler")
try:
inputValue = payload.get("A")
if inputValue == 10: # Simulate success
return {"result": "Success, received A = %s" % inputValue}
else: # Simulate error
raise Exception("Invalid input value")
except Exception as e:
logger.error("Error in handler: %s" % str(e))
raise
I got an error "No perspective session attached to this thread".
Error dispatching sendRequest onSuccess function:
org.python.core.PyException: java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: No perspective session attached to this thread.
at org.python.core.Py.JavaError(Py.java:545)
at org.python.core.Py.JavaError(Py.java:536)
at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:192)
at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction.__call__(ScriptManager.java:553)
at org.python.core.PyObject.__call__(PyObject.java:461)
at org.python.core.PyObject.__call__(PyObject.java:465)
at org.python.pycode._pyx45.successFunc$2(:13)
at org.python.pycode._pyx45.call_function()
at org.python.core.PyTableCode.call(PyTableCode.java:173)
at org.python.core.PyBaseCode.call(PyBaseCode.java:134)
at org.python.core.PyFunction.__call__(PyFunction.java:416)
at org.python.core.PyFunction.__call__(PyFunction.java:411)
at com.inductiveautomation.ignition.common.script.builtin.SystemUtilities$RequestImpl.dispatchFunc(SystemUtilities.java:1210)
at com.inductiveautomation.ignition.common.script.builtin.SystemUtilities$RequestImpl.finishSuccessfully(SystemUtilities.java:1200)
at com.inductiveautomation.ignition.gateway.script.GatewaySystemUtilities$SendRequestHandle.successResult(GatewaySystemUtilities.java:513)
at com.inductiveautomation.ignition.common.script.message.MessageHandlerRunnable.run(MessageHandlerRunnable.java:150)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalArgumentException: No perspective session attached to this thread.
at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.getSession(AbstractScriptingFunctions.java:104)
at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.operateOnSession(AbstractScriptingFunctions.java:118)
at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.operateOnPage(AbstractScriptingFunctions.java:47)
at com.inductiveautomation.perspective.gateway.script.PerspectiveScriptingFunctions.print(PerspectiveScriptingFunctions.java:704)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:190)
Help me to understand what I missed and better understand the call back function onSuccess .
Maybe someone will advise me on a simple way to execute the same task.
I am running Version: 8.1.47 Maker Edition
Thank you!
Hello Paul,
If your refresh-button is on the same view as the table. Couldn't u just directly refresh the data-property of the table through a button event?
So add a onActionPerformed-event to the button with the following code self.parent.parent.getChild("TableName").refreshBinding("props.data"). Adapt the self.parent.parent.getChild-part based on the structure within your view.
If u insist on using MessageHandlers, u would need te create a new MessageHandler at the perspective session event-level. Which would then send the message to the perspective view.
I am not familiar with the sendRequestAsync-method. So if this is not of any help, maybe someone else can ellaborate the use of this function further.
An async function runs in a different thread to the calling thread, so it does not have the ability to call perspective functions like system.perspective.* you have to use a message sent to a message handler on the perspective sessions and pipe your success message to it, in order for it to be able to open a popup.
Hi Royd,
Sometimes, the gateway message handler will take a longer time to update the table because it executes an RPC call and updates the table. If I put refreshBinding("props.data") directly after the sentmessage, it will get the old SQL table result.
Calling sendRequest from a Perspective session makes no sense. You already are on the gateway - all Perspective scripting and expressions are running on the gateway. sendRequest only makes sense for Vision, where scripts are truly running locally.
That is - you're introducing extra overhead to marshal results into a serialized form for no benefit on your part. If you want to invoke a long running action in Perspective, use system.util.invokeAsynchronous.
Okay, fair enough. invokeAsynchronous also doesn't have the nice onSuccess/onError callbacks.
It'd be really nice if Jython could automatically call through SAM/functional interface methods, so you could use CompletableFuture without so much ceremony...
What will be your go-to implementation? My concern is primarily the delay in the execution of RPC and database insert. I can simply refresh the table binding every 5 seconds, but it is not "nice" solution.
There's no RPC involved. Your script is starting on the gateway and ending on the gateway - that's a fundamental piece of the architecture of Perspective. Under the hood, we're using a websocket to shuttle all property values from the gateway/backend to your frontend session in the user agent, but you have no control over that, and nothing you're doing with arbitrary code (scripts, expressions) is running in the frontend in Perspective.
What I'm recommending is basically equivalent to what you have, you just get to define all the logic in one place (either in your script directly or, as Phil will recommend, in the project library) - and has the benefit of being able to send arbitrary data through to the callback without serialization overhead. The only thing the below example doesn't do is timeout automatically after a few seconds. If I had to do that, I'd look into Java's CompletableFuture API to 'chain' my actions together; it's not quite worth it IMO for the basic example here but incredibly powerful for composing asynchronous stuff together; see this post for some context to get started.