Update GUI via Asynchronous thread

I’m not sure I understand asynchronous threads correctly. I have the following in the action performed event of a button on a GUI.

def overwrite_database():
    try:
	    #Record event.
	    backend.messageHandler('HMI', 'info', 'HMI.overwrite_database()')
	
	    tbl = event.source.parent.parent.getComponent('tbl_offline')
	    data = tbl.data
	    backend.overwrite_database(data)
    #--------------------ERROR HANDLING--------------------
    except java.lang.Exception:
	    backend.messageHandler('HMI', 'info', 'Error location: overwrite_database()' + \
								 		      '\nError type: ' + str(sys.exc_info()[0]) + \
								 		      '\nError message : ' + str(sys.exc_info()[1]))
    except Exception:
	    backend.messageHandler('HMI', 'info', 'Error location: overwrite_database()' + \
							 		  	      '\nError type: ' + str(sys.exc_info()[0]) + \
							 		  	      '\nError message : ' + str(sys.exc_info()[1]))
    #--------------------ERROR HANDLING--------------------

system.util.invokeAsynchronous(overwrite_database)'

And then the function backend.overwrite_database(data) is called below from a script:

def overwrite_database(data):
    try:
	    #Record event.
	    backend.messageHandler('backend', 'info', 'backend.overwrite_database(data[...])')
	
	    ##########
	    #do some database related stuff
	    ##########

	    if i % 100 == 0:
		    strVal = strVal.rstrip(', ')
		    sql = strQry + strVal + ';'
		    args = []
		    system.db.runPrepUpdate(sql, args, db)	
				
		if (lastRow - 100) < 1:
			pass
		else:
			lastRow = lastRow - 100
			strQry = "INSERT INTO SCADA.flatFile (line, verName, modelDescrip, menGrp, staNo, seq, workCell, t_Sec, mCode, description, key_Point, quality_Chk, partNo, partName, scan, scan_req, pic, videoFileName) VALUES "
			strVal = ""	
				
		    new_Progress = ((float(max_new) - float(min_new))/(float(max_old) - float(min_old)))*(float(i)-float(max_old))+float(max_new)
		    future = InductionAutomation.assignLater(window.getRootContainer().getComponent('progressBar'), "value", int(new_Progress))
	    new_Progress = 100
	    future = InductionAutomation.assignLater(window.getRootContainer().getComponent('progressBar'), "value", int(new_Progress))

    #--------------------ERROR HANDLING--------------------
    except java.lang.Exception:
	    backend.messageHandler('backend', 'info', 'Error location: backend.overwrite_database()' + \
											      '\nError type: ' + str(sys.exc_info()[0]) + \
											      '\nError message : ' + str(sys.exc_info()[1]))
    except Exception:
	    backend.messageHandler('backend', 'info', 'Error location: backend.overwrite_database()' + \
										 	      '\nError type: ' + str(sys.exc_info()[0]) + \
										 	      '\nError message : ' + str(sys.exc_info()[1]))
    #--------------------ERROR HANDLING--------------------

And this thread, of which ive edited some parts out, calls a function I believe you created pturmel.

def assignLater(comp, prop, val, ms = 0):
    future = CompletableFuture()
    def assignLaterInvoked():
        try:
            try:
                setattr(comp, prop, val)
            except AttributeError:
                comp.setPropertyValue(prop, val)
            future.complete(None)
        except java.lang.Exception, e:
            future.completeExceptionally(e)
        except Exception, e:
            future.completeExceptionally(PythonAsJavaException(e))
    invokeLater(assignLaterInvoked, ms)
    return future

So is the last function sending my GUI calls back to the GUI from the asynchronous thread? In my original action performed event I call components on the GUI from an asynchronous thread. Am I leaving myself open for a world of hurt at some point? I’ve heard you mention to never ever do that but I’ve found no problems with the above code. It actually works really well and looks like a proper progress bar updating from 0 to 100.

Any more thoughts are appreciated.

Updating properties from an asynchronous thread will work. Until it doesn’t, and leaves your client deadlocked. That’s why you always have to call from the main thread.

2 Likes

Yes. And it isn't enough to just avoid writes. You must also avoid property reads. Property reads are really method calls and can have side effects. Only component methods and properties that are carefully designed to be accessible concurrently (written in java, not in jython) are safe in an asynchronous thread. The only thing you can do with a component in a background thread is to hold onto it for later passing back to the UI thread.

You risk client crashes and data corruption.

1 Like

You have peaked my curiosity. If interacting with the GUI from an asynchronous thread is so bad then why allow it?

I originally made the function asynchronous because it took longer than 1/10th of a second of GUI time (which you recommended somewhere else pturmel as a rule of thumb). And the function took so long I wanted to add a progress bar for the user. So is it impossible to update the progress of an asynchronous thread at this time? Any future plans for Ignition to allow that behavior? I’ve seen this functionality in other software so i don’t think it’s a novel idea.

Thanks for your thoughts. Always a pleasure to hear them.

1 Like

Besides getting the data from the table I think i'm following all processes correctly in my above example. Instead of using system.util.invokeLater() I use the assignLater you mention from later.py.

I uploaded 3 pictures of what is actually going on in my project. I changed my function backend.overwrite_database to accept 2 parameters and only reference them when using assignLater. I was incorrectly referencing the GUI in a couple of places including a custom popup. Hopefully, fingers crossed, this is the right path to follow for the time being. The only thing I don’t really understand is “futures”. I guess I’ll have to google it.

Java doesn't provide any way to prevent it. It is a rule for programmers using Swing. Programmers must follow the rule.

No, you can, but via invokeLater (or helpers like my assignLater that use invokeLater).

3 Likes