This is my first foray into both WatchService and multithreading.
I have an application that needs to detect when an external device issues a prompt for the user to do something. The prompt is delivered by way of an XML file dropped into a specific folder. The file is always titled "PRM.xml" and will overwrite previous PRM files if they exist. To prevent re-displaying old prompts, my code deletes the PRM file once displayed.
The application is user-interactive, so it is always listening for a PRM.xml in a background thread while the user is performing other activities on the main thread.
The problem is that when the user wants to end his session in the main thread (by typing sentinel "zzz"), the listening thread won't end, and the application only terminates if another event happens in the folder being monitored.
How do I force the background listening thread to quit when the user directs the main thread to terminate? (I hope I have included enough code to get a good response.)
// Method 'run' contains code to be executed in the thread
public void run() {
try {
// initiate new watch service to watch for new prompts
WatchService ws = dirToWatch.getFileSystem().newWatchService();
dirToWatch.register(ws, ENTRY_CREATE);
// monitor directory continuously until main program thread ends
while (!SmartTill.stCom.equals("ZZZ")) {
// get new directory events
WatchKey wk = ws.take();
// loop through all retrieved events
for (WatchEvent<?> event : wk.pollEvents()) {
if (event.context().toString().endsWith(fileToDetect)) {
System.out.println("DEBUG: Display Prompt, Delete PRM.");
// ...call to "displayPrompt" method goes here...
delFile(Paths.get(dirToWatch + fileToDetect));
}// end if
}// end for
// reset the key (erase list of events)
System.out.println("Key has been " +
(wk.reset() ? "reset." : "unregistered."));
}// end while
}// end try
catch(Exception e){}
}// end run
How do I force the background listening thread to quit when the user directs the main thread to terminate?
You should interrupt the thread. The WatchService.take()
method throws InterruptedException
. This means that when the main thread is done, it can interrupt the watch service thread. This will cause the take()
method to throw an InterruptedException
which gives the watch thread a chance to clean up and quit the while
loop and then return from the run()
method.
Thread watchThread = new Thread(new WatchRunnable());
watchThread.start();
// ...
// when the main thread wants the program to stop, it just interrupts the thread
watchThread.interrupt();
// although not necessary, main thread may wait for the watch-thread to finish
watchThread.join();
When you catch InterruptedException
, it is always a good pattern to re-interrupt the thread so that callers can also use the interrupt status:
WatchKey wk;
try {
wk = ws.take();
} catch (InterruptedException ie) {
// always recommended
Thread.currentThread().interrupt();
// leave the loop or maybe return from run()
break;
}
It is important to note that interrupting a thread is not some magical call. It sets an interrupt flag on the thread which causes certain methods which explicitly throw InterruptedException
to throw. If other parts of your code want to see if the thread has been interrupted, they should do something like the following.
if (Thread.currentThread().isInterrupted()) {
// clean up and exit the thread...
break;
}
Another solution would just be make the watcher thread be a daemon thread. This will mean that when the main thread finishes, the JVM will quit immediately and not wait for the watchThread
to finish. It's not as good as the interrupt above because it might stop the watchThread
right in the middle of it doing some important file IO.
Thread watchThread = new Thread(new WatchRunnable());
watchThread.setDaemon(true);
watchThread.start();