I think I've found the proper usage, cancelReading() should be called in the same thread as copyNextSampleBuffer(), but only after copyNextSampleBuffer() is unblocked.
For instance, keep state to track cancellation, let copyNextSampleBuffer() finish returning, and cancel reading before the next iteration:
while !shouldCancel, let buffer = output.copyNextSampleBuffer() {
...
}
if shouldCancel {
reader.cancelReading()
}