I have a Java application that is used to communicate with an embedded device over a UART connection (RS422). The host queries the microcontroller for data in 5 millisecond intervals. Up until recently I've been using ScheduledExecutorService scheduleAtFixedRate to call my communication protocol method, but it turns out scheduleAtFixedRate is very unreliable for this desired level of precision (as many other posts reveal). Among the data returned from the microcontroller is a timestamp (in microseconds), allowing me to verify the interval between received data packets independently of the JVM. Needless to say, the interval when using scheduleAtFixedRate varied wildly - up to 30 milliseconds between packets. Additionally, the scheduler will then try to overcompensate for the missed cycles by calling the Runnable several times within one millisecond (again, no surprise to anyone here).
After some searching, there seemed to be a consensus that the JVM simply could not be trusted to ensure any kind of precise scheduling. However, I decided to do some experimenting on my own and came up with this:
Runnable commTask = () -> {
// volatile boolean controlled from the GUI
while(deviceConnection) {
// retrieve start time
startTime = System.nanoTime();
// time since commProtocol was last called
timeDiff = startTime - previousTime;
// if at least 5 milliseconds has passed
if(timeDiff >= 5000000) {
// handle communication
commProtocol();
// store the start time for comparison
previousTime = startTime;
}
}
};
// commTask is started as follows
service = Executors.newSingleThreadScheduledExecutor();
service.schedule(commTask, 0, TimeUnit.MILLISECONDS);
The result of this was fantastic. Adjacent timestamps never varied by more than 0.1 milliseconds from the expected 5 millisecond interval. Despite this, something about this technique doesn't seem right, but I haven't been able to come up with anything else that works. My question is basically whether or not this approach is OK, and if not, what should I do instead?
(I am running Windows 10 with JDK 8_74)
Based on the information I've received in the comments, I've decided to use leave my code essentially intact (with the exception of Thread.yield() which I've added to the while loop). I have used this for a few months now and am very satisfied with the performance from this approach. See the final code below.
Runnable commTask = () -> {
// volatile boolean controlled from the GUI
while(deviceConnection) {
// retrieve start time
startTime = System.nanoTime();
// time since commProtocol was last called
timeDiff = startTime - previousTime;
// if at least 5 milliseconds has passed
if(timeDiff >= 5000000) {
// handle communication
commProtocol();
// store the start time for comparison
previousTime = startTime;
}
Thread.yield();
}
};
// commTask is started as follows
service = Executors.newSingleThreadScheduledExecutor();
service.execute(commTask);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With