How to store the Thread that results from the system.util.invokeAsynchronous call in a container property?

Hello I have a need to regularly poll a web service to updated state information and display on my vision screen.

Presently I am using a combination of invokeAsynchronous to do the web service calls with imbedded invokeLater to push the results to the window's root container.

This works, now I want to make sure that the invokeAsynchronous function exits when I navigate away from the screen.

When I was mistakenly using invokeLater for everything with the corresponding GUI freezes I just checked the if the root container was running or not.

If I can save the Thread that is returned as part of the invokeAsynchronous call I'm hoping that I can do a Thread.interrupt() in my propertyChanged handler and in the polling function I can check if the currentThread has been interrupted.

Unfortunately I can't seem to define a container property that is an object type, or a client tag either. Where can I stash the Thread object for later reuse by the root container's propertyChanged event handler?

I'm concerned that if I just define a tag to do this handshaking, I'll transition to another screen that would depend upon the same tag for the same thing. I'd rather not have to define as many tags as screens doing similar things.

Thank you,
Bill S.

BTW, Yes I can store the thread ID in a numeric field or thread Name in a string field and search through the getAllStackTraces set, but I was hoping I could just stash the Thread object somewhere.

I tried to do something similar in this post. There is a workaround proposed using the system globals dictionary in that thread.

Essentially, Ignition doesn’t currently expose Python objects natively, but rather as “script wrapper” objects. Would be cool if the dev team had many some progress on it :wink:

I highly recommend you not use a long-lived asynchronous thread for this. Use a client timer event that launches an asynchronous thread to do one poll. Place the thread's function in a library script so that it can use top-level variables as caches between executions. The timer event can do system.gui.getWindow() call to determine whether to fire the asynchronous task each time.

3 Likes

Depending on what type of data he is getting from the web polling, if it's simple enough to be stored in a client tag or a series of client tags or a dataset tag, he could write to those from the timer script, and then in his window bind the appropriate properties to the client tags - no need for having to do a system.gui.getWindow().

1 Like

Yes, but the OP doesn't want the polling at all if the window isn't open. Calls for a slightly different set of optimizations.

1 Like

Hello,

Thank you for the reply,

I did think about that, but decided that there isn't really a good way to determine if the prior async task had finished before launching another one.

Please explain your reasons behind your strong recommendation. I have been very informed upon reading other posts that you have made, so I'm sure there is a reason you feel my current way is not the best way.

I've been testing not just happy path so at times the request times out instead of returning quickly.

I do have shared scripting for all the actual interactions with the web service, but at times there are prompts and what not.

I have several windows that are basically doing the same cycle retrieving different data from the web service. And in some cases downloading large files.

Thanks again!

Where you care (and probably not everywhere), use a jython lock object declared at a top level of a project script to provide mutual exclusion. (Possibly with acquire timeout to bail out early.)

This is what I have that seems to work... The PropertyChanged on root container for window. The pollThreadId property is a type Long in the rootContainer.

rootContainer = event.source
if event.propertyName in ( "editor", "componentRunning" ):
	if event.newValue:
		pollThread = rootContainer.pollData()
		pollThread.setName(sourceName + "-PollData")
		rootContainer.pollThreadId = pollThread.getId()
	else:
		from java.lang import Thread
		pollThread = None
		for t in Thread.getAllStackTraces().keySet():
			if t.getId() == rootContainer.pollThreadId:
				pollThread = t
				break
		if not pollThread == None:
			pollThread.interrupt()
		

The pollData method returns the results of the invokeAsynchronous and contains a Thread.sleep() call to make it more friendly between web service calls.

Oy! That's expensive.

Other notes:

  • What kills your thread if a Vision client update arrives?
  • Are you sure componentRunning always goes false when the window closes?
  • pollThread == None will never be true (you cannot compare None with any form of equals), so you will try to interrupt None.
  • How are you telling your thread to run another poll? And how are you delivering results?

{ My original advice stands. Don't do this. }

2 Likes

How so? Is there a better way to get a list of all threads?

It seems to be going away because it seems the root container is stopped then restarted with the updates.

17:42:16.963 [AWT-EventQueue-0] INFO com.inductiveautomation.factorypmi.application.runtime.ClientPanel - Received project change notification: notificationMode=PUSH, updateMessage=com.inductiveautomation.ignition.common.gateway.messages.ProjectUpdate@1cfc4905
17:42:17.010 [AWT-EventQueue-0] INFO com.mwes.Shared.MWES.btnTesting.propertyChange - Name = componentRunning, Value True --> False
17:42:17.006 [ProjectUpdater] INFO Vision.ProjectUpdater - Starting download of 269,795 bytes
17:42:17.010 [W001_SG_LogViewer-PollData] ERROR com.mwes.Main Windows/SecsGem.W001_SG_LogViewer.pollData - Exception Caught.  Thread W001_SG_LogViewer-PollData, Interrupted False
17:42:17.010 [W001_SG_LogViewer-PollData] INFO com.mwes.Main Windows/SecsGem.W001_SG_LogViewer.pollData - Finish PollIt, Thread W001_SG_LogViewer-PollData

-----SNIP -----

Main Windows/SecsGem/W004_SG_LogViewer
Null Swap
_::_
17:42:17.223 [AWT-EventQueue-0] INFO com.mwes.Main Windows/SecsGem.W001_SG_LogViewer.pollData - Start
17:42:17.224 [AWT-EventQueue-0] INFO com.mwes.Main Windows/SecsGem.W001_SG_LogViewer.propertyChange - Poll Thread Thread[W001_SG_LogViewer-PollData,6,main], W001_SG_LogViewer-PollData
17:42:17.226 [AWT-EventQueue-0] INFO com.mwes.Shared.MWES.btnTesting.propertyChange - Name = componentRunning, Value False --> True
17:42:17.224 [W001_SG_LogViewer-PollData] INFO com.mwes.Main Windows/SecsGem.W001_SG_LogViewer.pollData - Start PollIt, Thread W001_SG_LogViewer-PollData

I have always received a event notification telling me when components have started or stopped.

Ok, I don't configure my screens to cache, because then the stop notification will be delayed until the screen actually goes away. So far except for some PanelView die hards (anything other than PanelViews shouldn't be used y'know) I haven't had many complaints about the slow drawing of the screens.

Thank you,! It's been awhile since I've had to do Python and I forgot the correct syntax for this. Seemed to work ok.

I'm not telling it to run another poll, it does a Thread.sleep to be polite, then it does the poll. For the properties I'm calling an InvokeLater routine to push the updated data. For the client tag based stuff I just write it to the tags.

Please explain WHY this is such a bad idea, rather than possibly 10 timers with the problem of making sure I'm checking MY session's windows for presence or watching a screen specific client tag, then of course I'm back to relying upon the root container event processing again. I would then also have to define tags for sharing the retrieved information with the screens rather than just pushing to the table data properties directly.

Thank you for your reply
Bill

Yes: ThreadGroup.enumerate(). Doesn't require producing a stack trace for every thread.

The quirk is that you have to use ThreadGroup.activeCount() to obtain an estimate of the number of threads from which to generate an empty array (with jarray) to hold the threads. (I double the estimate.)

Hmm. OK. Last I looked under the hood, I thought I only saw the mechanism for False => True.

OK.

It seems you've worked out the odd corners for your case. First I've seen where the window itself started the thread. To me, starting a long-lived thread with the client startup (via top-level object in a script module), moderated through system.util.getGlobals() to kill off only when a project is updated, is easier to reason about. After each sleep, and interrupt check, it could check for the window's presence to determine whether to poll, and go to sleep again in either case.