Question on stopping asynchronous threads in Ignition

I'd like to share my findings on the issue of running and managing asynchronous threads in Ignition, for anyone going down the same rabbit hole, but also curious if this is a big no-no in Ignition. So far we haven't had any issues with this but I am curious if anyone else has done something similar and achieved success with it.

We were trying to create asynchronous threads that can start/stop listening to a serial port opened up on a client. We wanted to keep this in vision and have this "driver" communicate to perspective as well by writing to a UDT that perspective reads. We could have went with SFC's alternatively, or an external .NET service running on the client that talks to OPC, but these seem to be "unclean" solutions.

So to do this, we went with making asynchronous threads to open/close serial communication to the machine plugged into the local asset.

In order to close/end the serial communication, we have to shut off the asynchronous call which we can do with a timeout on no data change to end the thread, but also, we found that we can use the following to locate rogue or orphaned threads:

def get_thread(name):
	# Get the Java Thread object in java.lang.Thread
	from java.lang import Thread
	# Get all of the currently running threads from the current java stack trace
	threads = Thread.getAllStackTraces()
	
	# Assume only one thread exists that matches name, modifiable to return multiple threads if more than 1 match
	result = None
	for thread in threads:
		thread_name = thread.name
		# Thread name matches known input thread name
		if str(thread_name) == str(name):
			result = thread
			break
	return result

Followed by application of

def stop_thread(name):
	thread = get_thread(name)
	thread.stop()

Using thread.stop() is sort of frowned upon, but does it cause issues like not being garbage collected, etc.? I don't know that I have seen this solution in the forums. We have been able to shut off calls from thread generators like

def make_thread(name, target=None, callback=None, timeout=60):
	import threading
	thread = threading.Thread(target=target, name=name, kwargs={"timeout":timeout, "callback":callback})
	thread.start()
	return thread

or

def make_ign_thread(name, target=None, callback=None, timeout=60):
	thread = system.util.invokeAsynchronous(function=target, description=name, kwargs={"timeout":timeout, "callback":callback}))
	# Probably need to store this thread name in a DB or tag, it looks like [Thread-23]
	thread_name = thread.getName()
	return thread

that are no longer persistent in the current context (i.e. the script stopped but the threads continue to exist).

Anyone have experience doing this and can chime in on the pros/cons of this? Will the world be forever altered by my blasphemy? Will I regret this in the next 137 years of my existence?

Cheers,
Roger

2 Likes

This may not answer all your questions, but it is good advice.

The reasons not to manually call stop are highlighted in the javadoc:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Thread.html#stop()

interrupt() is safer, to indicate to a thread that it should stop a busy-wait: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Thread.html#interrupt()

If you find yourself making arbitrary threads for your own purposes often, I would highly recommend using one of the executor factories to create a thread pool once, to which you can dictate your own work:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Executors.html#newFixedThreadPool(int)

Any threads you submit to that execution pool should either monitor their own sentinel signal or just check Thread.currentThread.isInterrupted() so you can propagate cancellation cleanly.

2 Likes