url = self.parent.parent.getChild('txt_URL_All').props.text
client = system.net.httpClient()
promise = client.getAsync("https://httpbin.org/delay/3",params={"a": 1, "b": 2})
system.perspective.print("Assigning callback for call to: " + url)
promise.whenComplete(recvDataCallback)
#promise.get()
system.perspective.print('Callback attached and we should get a triggered soon')
def recvDataCallback(arg):
#def recvDataCallback():
system.perspective.print("Callback triggered")
## do something out of band
msgType = "ReadAllData"
I just think the print isn’t working because when the callback completes on a different thread there’s no implicit Perspective session for the print call to use.
If you stored the sessionId and then specified it when calling system.perspective.print it might work.
for interest sake, i replaced the memory tag with a Session Custom Property. No change in functionality, but probably should have used that in the first place.
This should do what you are looking for, when you are reaching the promise.whenComplete it is already finished evaluating and should return immediately, you are looking for your response.
promise.get() will block at then end though and wait for the response before continuing to execute
Also no need to worry about a tag, as you can use the sessionid as a parameter to your callback.
If you call promise.get() in the same thread as the .getAsync(), you’ve just converted the operation to synchronous. Equivalent to just using the blocking .get() method. If someone actually needs asynchronous operation, they can’t do this.
@pturmel is correct, what I provided will only work if you want to execute more scripting before dealing with your return in the same thread
If you want it to fully execute asynchronously then you are able to do that with the .getFuture method on the promise.
A few notes though:
You require a page ID in your callback, which I dont 100% understand since its in the same session?
I tried passing the function a parameter on the future.thenRun() however, Ignition does not like this and will give you a Null Pointer Exception (I assume it has something to do with the fact thenRun is expecting a java runnable, and not a python function call?)
Client script:
def recvdCallbackFunction():
tags = system.tag.readBlocking(["[default]localSessionId", "[default]localPageId"])
system.perspective.print("Callback Triggered by function in Script", sessionId=tags[0].value, pageId=tags[1].value)
system.perspective.print("Callback Triggered by function in Script without PageId", sessionId=tags[0].value)
sessionId = self.session.props.id
pageId = self.page.props.pageId
system.tag.writeBlocking(["[default]localSessionId", "[default]localPageId"],[sessionId, pageId])
system.perspective.print("SessionId:" + sessionId + ", PageId: " + pageId)
client = system.net.httpClient()
promise = client.getAsync("https://httpbin.org/delay/3",params={"a": 1, "b": 2})
system.perspective.print("Assigning callback for call to: " + sessionId)
#Get the CompletedFuture java class for the promise
future = promise.getFuture()
#Set the callback for your future response
future.thenRun(recvdCallbackFunction)
Interesting update, it looks like if you reference the variables in your python script within the callback, they are still present and so you can actually execute this without tags involved. However I am not sure if this is intended functionality or not.
It's because they're going to execute in different threads; httpClient async actions run in a dedicated pool of threads per-scope, and direct Perspective scripts run in a distinct pool of threads. Perspective scripting actions 'keep track' of what page, session, view, etc they're operating on via Java ThreadLocals - so as soon as you 'lose' the thread, you need to maintain that state yourself.
I tried passing the function a parameter on the future.thenRun() however, Ignition does not like this and will give you a Null Pointer Exception (I assume it has something to do with the fact thenRun is expecting a java runnable, and not a python function call?)
I'm interested in what this NPE is; Jython usually tries to automatically cast SAM (single abstract method) classes from functions. Either way, I'm not sure why Promise.then() or whenComplete() wouldn't work for these purposes; remember that your callback function should accept some argument, even if it's going to be ignored.
That's intentional/part of the way Python does closures, yes. Another option if you want to be explicit about providing values to 'callback' functions that you don't have control of the call site is functools.partial.
They just never execute the function, error out, or create any gateway logs.
Here is a script that throws it (It is extremely long), I just put it on a button in an 8.0.12 view. Interestingly it also executes the callback immediately without waiting for the response. You will see that all three statements are printed before the error happens.
def recvdCallbackFunction(paramSessionId, paramPageId):
system.perspective.print("Callback Triggered by function in Script", sessionId=paramSessionId, pageId=paramPageId)
sessionId = self.session.props.id
pageId = self.page.props.pageId
system.perspective.print("SessionId:" + sessionId + ", PageId: " + pageId)
client = system.net.httpClient()
promise = client.getAsync("https://httpbin.org/delay/3",params={"a": 1, "b": 2})
system.perspective.print("Assigning callback for call to: " + sessionId)
future = promise.getFuture()
future.thenRun(recvdCallbackFunction(sessionId, pageId))
I never updated, but when I played with this last week my takeaway was that the http client async functions work totally fine, but I never got system.perspective.print to work from another thread, even if I had the session id.
The callbacks were definitely happening though.
One of the fundamental problems with OP’s original code was that he defined the callback function after referencing it, which everyone in subsequent posts, including me, seems to have fixed without mentioning explicitly.
Thanks everyone, I was so focused on the callback side of things, I made an assumption about declaring the def in the wrong spot.
Kevin.Herron and PGriffith both made excellent points, and now I have what I wanted working. I’m extremely grateful for the assistance and hopefully this helps some other poor beginner too.