# Managing multiple asynchronous threads

**URL:** <https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185>\
**Category:** Ignition\
**Created:** [July 29, 2020, 1:50pm UTC](https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185 "2020-07-29T13:50:58Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![dkhayes117](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/dkhayes117/32/68344_2.png) [@dkhayes117](https://forum.inductiveautomation.com/u/dkhayes117)\
**Post date:** [July 29, 2020, 1:50pm UTC](https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185/1 "2020-07-29T13:50:58Z")

</div>

I've followed this post for monitoring a directory for file changes and I think I have created more than one watch service in different threads. How do I monitor, manage, and delete these threads?

> [@Gateway Script to monitor directory for new files](http://forum.inductiveautomation.com/t/gateway-script-to-monitor-directory-for-new-files/16124/22):
>
> I wanted to update with a solution for an obstacle I ran into when trying to open files identified by this watcher. I saw the following regardless of using WatchService.take() or WatchService.poll(). File paths are reliably collected and always immediately verifiable with os.path(path) returning True Upon immediate inspection after a WatchService notification, some files return False from Files.isReadable(path) and will throw IOError[2] No such file if opened After a short delay (i.e. 3 secon…

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 29, 2020, 2:17pm UTC](https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185/2 "2020-07-29T14:17:41Z")

</div>

To manage long-running jython threads through scripting restarts, you need to:

1. Place the thread object in a dictionary that retains its content through script restarts. I used to recommend the dictionary from system.util.getGlobals(), but that is no longer suitable. I created a free [Life Cycle Module](http://forum.inductiveautomation.com/t/system-util-getglobals-not-persisted-across-designer-save/33869/14) that provides such dictionaries going forward.

2. Write the asynchronous function’s loop so that it regularly checks [Thread.interrupted()](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#interrupted--), particularly after any java I/O operations. Clean up and return when true.

3. Write the asynchronous function’s startup section to grab the persistent dictionary, look up any thread already on its own key, and if present, `.interrupt()` and `join()` it. Then place the function’s own thread, `Thread.currentThread()`, into the persistent dictionary in that same key. Then proceed with the rest of your startup logic (including picking up any saved state from a prior thread’s cleanup).

---

<div class="post-metadata">

**Author:** ![dkhayes117](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/dkhayes117/32/68344_2.png) [@dkhayes117](https://forum.inductiveautomation.com/u/dkhayes117)\
**Post date:** [July 29, 2020, 3:31pm UTC](https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185/3 "2020-07-29T15:31:29Z")

</div>

> [@pturmel](#):
>
> Place the thread object in a dictionary that retains its content through script restarts. I used to recommend the dictionary from system.util.getGlobals(), but that is no longer suitable. I created a free [Life Cycle Module](http://forum.inductiveautomation.com/t/system-util-getglobals-not-persisted-across-designer-save/33869/14) that provides such dictionaries going forward.

Starting with number 1, I'm modifying the script like so. Is this correct?  
OLD

```auto
	if "fw" not in system.util.getGlobals():
		system.util.getGlobals()["fw"] = FileWatcher()

```

NEW

```auto
	if "fileWatcher" not in system.util.persistent():
		system.util.persistant("fileWatcher") = FileWatcher()

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 31, 2020, 12:30pm UTC](https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185/4 "2020-07-31T12:30:05Z")

</div>

No, my system.util.persistant returns a dictionary, too, but the argument lets you isolate scopes more conveniently.

---

<div class="post-metadata">

**Author:** ![dkhayes117](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/dkhayes117/32/68344_2.png) [@dkhayes117](https://forum.inductiveautomation.com/u/dkhayes117)\
**Post date:** [July 31, 2020, 2:16pm UTC](https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185/5 "2020-07-31T14:16:53Z")

</div>

should be like this then

```auto
if "fileWatcher" not in system.util.persistent():
		system.util.persistant()["fileWatcher"] = FileWatcher()

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 31, 2020, 7:23pm UTC](https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185/6 "2020-07-31T19:23:48Z")

</div>

No, more like this:

```auto
persist = system.util.persistent("someUniqueConstantName")
oldFileWatcher = persist.pop("fw")
persist['fw'] = FileWatcher(oldFileWatcher)

def getWatcher():
    return persist['fw']

```

Note the oldFileWatcher passed to the new one so the new one’s thread can tell it to shut down.

---

<div class="post-metadata">

**Author:** ![dkhayes117](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/dkhayes117/32/68344_2.png) [@dkhayes117](https://forum.inductiveautomation.com/u/dkhayes117)\
**Post date:** [August 5, 2020, 12:51pm UTC](https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185/7 "2020-08-05T12:51:59Z")

</div>

Am I missing anything? I’m not sure if I still need the `kill()` and `run()` functions

```auto
from java.nio.file import FileSystems,Files
from java.io import File
from java.nio.file import StandardWatchEventKinds as swek
from java.util.concurrent import TimeUnit
	
class FileWatcher():
	isRunning = False
	def __init__ (self):
		self.run()
	
	def _doAsync(self):
		self.isRunning = True
		watcher = FileSystems.getDefault().newWatchService()
			
		p = File("\\\\L:\\Files\\CFC MINIPLOTS").toPath()
		#p = File("\\\\hid-ign-01\\Cutting Miniplots").toPath()
		key = p.register(watcher,swek.ENTRY_CREATE,swek.ENTRY_DELETE,swek.ENTRY_MODIFY)
		while self.isRunning:
			k = watcher.poll(5,TimeUnit.SECONDS)
			if k:
				for e in k.pollEvents():
					kind = e.kind()
					
					if kind == swek.ENTRY_DELETE:
						filePath = p.resolve(e.context()).toString()
						system.tag.write('Cutting/watchService/deletedFile',filePath)
						
					if kind == swek.ENTRY_MODIFY:
						filePath = p.resolve(e.context()).toString()
						system.tag.write('Cutting/watchService/modifiedFile',filePath)
						
					elif kind == swek.ENTRY_CREATE:
						filePath = p.resolve(e.context()).toString()
						system.tag.write('Cutting/watchService/newFile',filePath)
						Files.probeContentType(p.resolve(e.context()))
							
				k.reset()				
				
				
	def kill(self):
		self.isRunning = False
		
	def run(self):
		if not self.isRunning:
			system.util.invokeAsynchronous(self._doAsync)
		
persist = system.util.persistant('miniplotWatcher')
oldFW = persist.pop('fw')
persist['fw'] = FileWatcher(oldFW)
			
def getWatcher():
	return persist['fw']

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [August 5, 2020, 1:34pm UTC](https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185/8 "2020-08-05T13:34:16Z")

</div>

Multiple problems.

- Mis-spelled `persistent`.
- You’ve no argument to **init** () to capture `oldFW` and `.interrupt()` it.
- You’re not regularly checking `Thread.interrupted()` in your `run()` method.
- You are not running in another thread. To interrupt your FileWatcher directly, you need to inherit from `java.lang.Thread` and use `.start()`.

---

<div class="post-metadata">

**Author:** ![dkhayes117](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/dkhayes117/32/68344_2.png) [@dkhayes117](https://forum.inductiveautomation.com/u/dkhayes117)\
**Post date:** [August 5, 2020, 1:39pm UTC](https://forum.inductiveautomation.com/t/managing-multiple-asynchronous-threads/37185/9 "2020-08-05T13:39:00Z")

</div>

@pturmel Java noob try #2. Not sure if I am passing _oldFW_ correctly and `self.interrupted()` doesn’t seem right.

```auto
from java.lang import Thread
from java.nio.file import FileSystems,Files
from java.io import File
from java.nio.file import StandardWatchEventKinds as swek
from java.util.concurrent import TimeUnit
	
class FileWatcher():
	isRunning = False
	
	def __init__ (self, oldFW):
		oldFW.interrupt()
		oldFW.join()
		self.run()
		
	def _doAsync(self):
		self.isRunning = True
		watcher = FileSystems.getDefault().newWatchService()
				
		p = File("\\\\L:\\Files\\CFC MINIPLOTS").toPath()
		#p = File("\\\\hid-ign-01\\Cutting Miniplots").toPath()
		key = p.register(watcher,swek.ENTRY_CREATE,swek.ENTRY_DELETE,swek.ENTRY_MODIFY)
		while self.isRunning:
			k = watcher.poll(5,TimeUnit.SECONDS)
			if k:
				for e in k.pollEvents():
					kind = e.kind()
					
					if kind == swek.ENTRY_DELETE:
						filePath = p.resolve(e.context()).toString()
						system.tag.write('Cutting/watchService/deletedFile',filePath)
						
					if kind == swek.ENTRY_MODIFY:
						filePath = p.resolve(e.context()).toString()
						system.tag.write('Cutting/watchService/modifiedFile',filePath)
							
					elif kind == swek.ENTRY_CREATE:
						filePath = p.resolve(e.context()).toString()
						system.tag.write('Cutting/watchService/newFile',filePath)
						Files.probeContentType(p.resolve(e.context()))
				k.reset()
				if self.interrupted():
					kill(self)				
					
					
	def kill(self):
		self.isRunning = False
			
	def run(self):
		if not self.isRunning:
			system.util.invokeAsynchronous(self._doAsync)

persist = system.util.persistent('miniplotWatcher')
oldFW = persist.pop('fw')
persist['fw'] = FileWatcher(oldFW)
						
def getWatcher():
	return persist['fw']

```
