Update GUI via Asynchronous thread

I’ve got a script I am experimenting with that can take a lot of time to execute, so I now use the system.util.invokeAsynchronous to run the script in a separate thread to keep the GUI from locking up. I am using the asynchronous thread to copy files from one place to another, and I would like to show the progress of the file copy with a progress bar. How would I go about this?
I am importing a few python libraries to do this.

import shutil

def fileTransfer():
	path = system.file.openFile()
	newPath = "F:\NewFolder"
	shutil.copy2(path, newPath)

system.util.invokeAsynchronous(fileTransfer)
2 Likes

Define a function in your fileTransfer function that you call with system.util.invokeLater.

Example:

import shutil
label = event.source.parent.getComponent("Label")
def fileTransfer():
	path = system.file.openFile()
	newPath = "F:\NewFolder"
	shutil.copy2(path, newPath)
	def func():
		label.text = "Step one done"
	system.util.invokeLater(func)
system.util.invokeAsynchronous(fileTransfer)

Functions called with invokeLater are always run on the GUI thread so this is safe.

Best,

1 Like

Since this sort of thing pops up often, I created a set of wrappers that simplify this stuff, and allows you to directly use functions in your script modules. The wrappers take care of stuffing the arguments you supply into a temporary fuction’s default argument.
For your case, try assignLater(). Nick’s example becomes:

# In a script module in your project:
import shutil
def fileTransfer(label):
   path = system.file.openFile()
   newPath = "F:\NewFolder"
   shutil.copy2(path, newPath)
   shared.assignLater(label, "text", "Step one done")
# In your button event
label = event.source.parent.getComponent("Label")
shared.later.callAsync(project.module.fileTransfer, label)

later.py (12 KB)
{ Updated link to point to a permanent location @ automation-pros.com }

3 Likes

This link seems to be broken... or I'm broken. One or the other.

Updated for you, with a non-forum permanent location. (This is a newer version of later.py – such things evolve over time.)

1 Like

Opened the file with a tiny hope in my heart that there would be an invokeLater that can be awaited inside an async thread :wink:

jython2/python2 doesn’t have await. Asyncio and general threading don’t play well together. When it becomes availably in jython (if it ever gets to 3.x), don’t be surprised to find it forbidden in any foreground thread.

I don’t want to await in the UI thread, I wan’t to be able to await a function called from and inside an async thread.

Example:

def asyncInvoked():
	
	def laterInvoked():
		system.gui.messageBox("Please press OK")

	system.util.invokeLater(laterInvoked).await()

	# Do more stuff after Ok is pressed.

system.util.invokeAsynchronous(asyncInvoked)

Coroutines run in the same thread they are called from, so even if asyncio was available, you aren’t going to be able to await in a background thread for a coroutine to run in a gui thread.

If you absolutely have to pass something back from the UI thread, supply a future for the GUI thread to complete and the async thread to .get().

OK. I’ve poked myself with the futures idea. Grab the latest version of later.py, then try this:

def asyncInvoked():
	# do something in the background
	shared.later.callLater(system.gui.messageBox, "Please Press OK").get()
	# do something after OK is pressed

system.util.invokeAsynchronous(asyncInvoked)

Note the use of .get() to wait for system.gui.messageBox to complete.

This updated later.py includes an emulation of invokeLater for gateway scope. Untested and unsupported!

Edit: Realized the invokeLater emulation should only use one thread. Fixed.

3 Likes

Raises an error: AttributeError: 'NoneType' object has no attribute 'get'
Think the later.py file is the same as the later_old.py file
`

Refresh your cache. They are definitely different files.

That was actually the problem. Thank you

The callLater function works great. There is just a minor bug when a Python exception is thrown.
I made the following workaround (and for all the other functions in the later.py file aswell), don’t know if you can come up with something better.

def callLater(func, *args, **kwargs):
	future = CompletableFuture()
	def callLaterInvoked(f=func, a=args, kw=kwargs):
		try:
			future.complete(f(*a, **kw))
		except java.lang.Exception, e:
			future.completeExceptionally(e)
		except Exception, e:
			future.completeExceptionally(java.lang.Exception(str(e)))
	invokeLater(callLaterInvoked)
	return future

Hmm. Will play with this later. PyException extends java's RuntimeException, so I thought it would work. /-:

The linked version of later.py has been updated to wrap python exceptions to (mostly) retain their traceback when captured by the utility functions and delivered to a future.

1 Like

I apologize for my gross ignorance of this topic. I am self-taught in Python due to Ignition. What should I do with later.py?

I have downloaded it (from the Automation Pros website), created a script called “later” in the shared script library and copied and pasted the contents of later.py into it. Two things are confusing me:

  1. Phil’s comment of “refresh your cache”. This implies (to me at least) that later.py is NOT just a shared script in the script library since presumably any changes to it would be available as soon as I hit the commit button.

  2. The syntax of Phil’s earlier post:

# In a script module in your project:
import shutil
def fileTransfer(label):
	path = system.file.openFile()
	newPath = "F:\NewFolder"
	shutil.copy2(path, newPath)
	shared.assignLater(label, "text", "Step one done")

# In your button event 
label = event.source.parent.getComponent("Label")
shared.later.callAsync(project.module.fileTransfer, label)

I would have thought that the syntax would be:

shared.later.assignLater(label, "text", "Step one done")

Again, I apologize for my ignorance.

  1. That comment was directed solely to Claus, in regards to him downloading an updated version of later.py from my public website.

  2. You are correct. That was a typo.

  3. (You didn’t ask, but…) The usage of system.file.openFile() should be in the event, prior to the callAsync(), as it opens a dialog for picking a file.

2 Likes

Ah. The comment about system.file.openFile() makes sense. So the example should read something more like this?

# In a script module in your project:
import shutil
path = system.file.openFile()
def fileTransfer(label,path):
	newPath = "F:\NewFolder"
	shutil.copy2(path, newPath)
	shared.later.assignLater(label, "text", "Step one done")

# In your button event 
label = event.source.parent.getComponent("Label")
shared.later.callAsync(project.module.fileTransfer, label,path)

Just in case any of my fellow noobs need something to copy and paste.

1 Like