When invoking a system.tag.writeAllSynchronous() I understand that ‘something’ pauses and waits until the write is done.
But what excactly pauses? Is it the whole gateway? Is it the script the function is invoked in? Or?
I have a script where I want to make sure that the first line (basically a system.tag.writeAll()) is executed before the next line is executed and thought maybe the system.tag.writeAllSynchronous() would do that, but I’m unsure of the actual consequenses.
A side questeion:
If I use system.util.invokeLater() with a function that has a return value, how do I grab that return value and pass it into a variable?
The system.tag.writeAllSynchronous will stop your current runtime.
But Ignition does have multiple runtimes.
If you’re calling it from a client, it will lock up the client interface. If you’re calling it from a tag-changed script, the tag polling will be stopped. If you’re calling it from a gateway event, you can choose between a dedicated or shared thread. A shared thread will stop a big part of the gateway. A dedicated thread will run in parallel.
As for your question on how to return data for async functions. The easiest way is probably to write a response to a memory or client tag. Then you can react on that tag change to proceed with your code.
But do watch out that, while you’re waiting for anything write to finish (sync or async), anything could happen to your tags.
Just to be clear, if I have some lines of code and I invoke a system.tag.writeAllSynchronous() the next line won't be executed before the write is done?
Welcome to the world of asynchronous programming! You need to catch up a lot on it. Basically in asynchronous programming you delegate a task requiring an I/O such as read or write from a disk or socket etc to the OS and supply it with a call backfunction and continue execution to the next line after the asynchronous call. When the asynchronous task is completed by the operating system, it executes your call back function where you do the next steps. If your next line of code depends on the result of I/O then you have to put those next lines of code in the call back function or use synchronous I/O function that waits for completion of I/O. This is not easy and requires careful design of code. There are a lot of pros and cons of Asynchronous programming its better to do a study first in order to start using it correctly.
Yes, it will wait until the write is successful (or failed, you can see that in the returned value) before executing the next line. Scripts from other runtimes do keep working.
It all depends on your situation. Most often you can ensure nothing else can write tot that tag (no other script, but also not the PLC itself) while you're busy (with some sort of handshake protocol). Otherwise you just have to prepare yourself to fail gracefully, or perhaps it doesn't matter that much that one change every now and then is missed.
Is there a better way to wait before executing the next line in a script?
I’m doing a system.tag.writeAll(), the next line does a system.tag.readAll() on the same tags that were written and then the last line displays the read data in a table.
The issue is that sometimes the read and display lines executes faster than the write which results in the table showing wrong data.
That’s why I want to wait for the write to be done.
The sync writeALL and readALL should actually wait for all tags to be written or read before falling thru the wait. If its not then Ignition scripting experts like kevin McKluskey etc should investigate why this is not happening! The Read/Write-ALL functions are provided to avoid round trips to server while reading/writing a set of parameters from/to server by specifying tags to be read/written in an array.
I was told that the synchronous readAll/writeAll are deprecated in version 8 and internally they use asynchronous read/write so as to maintain backward compatibility with sync read/write functions. Which version of Ignition you are using 8 or less than that?
Why do you want to read the tags immediately after you have written them?
writeAll is an asynchronous function, so it moves on to the readAll before the writes have actually completed.
Read all on the other hand is not asynchronous. It will read the values of the tags at that time which may or may not be after the write has occurred.
What are you expecting to happen to the tag values that you need to read them again after the write? Is the controller modifying them?
You could use invokeLater with the delay to wait until all processing and pending events plus your delay are finished to make the call.
Something like this:
#code leading up to the tag write
system.tag.writeAll(tagPaths,tagValues)
#if you need to pass parameters to the function then you must use default parameters
#invokeLater will call the function with no parameters sent.
def updateTable():
system.tag.readAll(tagPaths,tagValues)
#do any processing dependent on the tag read here.
#this will wait until all currently processing and pending events are completed
#plus 10 sec's (the delay is in Milliseconds) to call the updateTable function
system.util.invokeLater(updateTable,10000)
If you don’t know how long the task in the controller is going to take then you will need to look into using invokeAsynchronous in conjunction with invokeLater.
Everything @lrose said is correct, although I'll bring up one important clarification - readAll will read the tags synchronously, but the tags themselves are still only receiving values from the OPC item (ie, PLC) at their given scan class rate - so performing system.tag.read(All) will not necessarily give you the values you just wrote, even if you used writeSynchronous. To guarantee you are receiving accurate OPC values, you would have to use system.opc.readValues() to trigger a synchronous, direct read of the OPC item.
@lrose
The function I’m talking about is a user making a backup of OPC values into memory tags.
After the OPC tags are written to the memory tags I want to read the memory tags and present their values to the user so he can verify that the correct values are backed up.
Thus the read after write issue.
So by replacing system.tag.readAll() with a system.opc.readValues() the issue would be resolved?
system.opc.readValues() reads values from an OPC device, such as a PLC. So it will not work with memory tags, unless the memory tags are also in the device. When you say memory tag, I assume you mean an Ignition memory tag that isn’t tied to an OPC path.
If that is indeed the case then you would still need the system.tag.readAll(). You have a couple of choices then.
1.) use a writeAllSynchronous knowing that you will block whatever thread it is called on (yes that means the UI will lock up if you call it from an event script) and then call readAll() if this is the case I would recommend that you look into invokeAsynchronous to do the writes on a dedicated thread.
2.) use writeAll and invokeLater with the delay as I showed above.
3.) Probably the simplest is to have the OPC values and Memory values shown on the same window and have the operator wait until all of the memory values match the OPC values as confirmation.
If it is system.tag.writeAll(), then the writes occur in the background and the invokeLater will wait until the ‘function call to writeAll’ returns and then an additional 1 second.
Because writeAll is writing the tags Asynchronously it will return prior to the writes being completed.
If this is the case then increasing the delay will help insure that the tag values have been updated prior to the read.
Remember if you are using system.tag.readAll() to read the values you not only have to allow enough time for the write to complete but you also need to allow enough time for the tags to be scanned and read from the device. This will depend upon the longest scan class that any of the tags are in. So if it takes 1 second for the tags to be written and all of the tags are in a 1 second scan class you will need a delay at a minimum of 2 seconds to insure that the tag values have been written by ignition and read by ignition.
You can shortcut that by reading the OPC values directly using system.opc.readValues()
The only way to guarantee the writes are performed first and not lock up the client, is to use invokeAsynchronus and perform the write using writeAllSynchronous on a separate thread from the UI.
“You can shortcut that by reading the OPC values directly using system.opc.readValues()”
They are all memory tags.
“Remember if you are using system.tag.readAll() to read the values you not only have to allow enough time for the write to complete but you also need to allow enough time for the tags to be scanned and read from the device.”
Good point. Does this also apply to memory tags?
“The only way to guarantee the writes are performed first and not lock up the client, is to use invokeAsynchronus and perform the write using writeAllSynchronous on a separate thread from the UI.”
@pturmel
“And that thread can call invokeLater to deliver the results to the UI.”
I see. How do I do that? Could you expand on my pseudo code?
# you must use default parameters because invokeAsynchronous and invokeLater can not send parameters with the function call
# paths: A list of paths to write the values to
# values: A list of values to write, values in values must be in the same order as the paths
def updateValues(paths = ['place your tag paths here'],values = ['place your tag values to write here'],rootContainer = event.source.parent):
system.tag.writeAllSynchronous(paths,values)
def sendBack(paths = paths, rootContainer = rootContainer):
values = system.tag.readAll(paths)
#do any processing dependent on the tag read here
#since we have passed in a reference to the rootContainer we can modify UI objects
system.util.invokeLater(sendBack)
system.util.invokeAsynchronous(updateValues)
The reference to the rootContainer is needed so that the functions called on the asynchronous thread can access the UI elements. This is a long running process and since it is asynchronous the event that initiates thread has already completed and returned so any reference to the events parent (unless passes into the thread) may be killed resulting in an error.
There are very specific and important patterns which need to be adhered to when multi-threading to insure that your program executes without error.
For instance in your pseudo code you utilize a parms variable, however, it is not shown as being passed into the write function, this could result in an error if in truth you are not utilizing a default parameter in the write function to insure that the parms variable reference is kept alive.
To use @pturmel excellent optimization suggestion you would simply call the read_data function following the write_Data and pass the results to the show function.
Also, since you are now doing this asynchronously and there is a guarantee that the write will happen prior to the read, there is no need for the delay in the invoke later call.