I had originally planned to defer learning about how to cancel out of an asynchronous operation, but the unexpected “timeout doesn’t time out” behavior of asynchronous serial port read gave me the… opportunity… to learn about cancellation now.
My first approach was hilariously clunky in hindsight: I found
Task.WhenAny first, which will complete the
await operation if any of the given list of
Task objects completed. So I built a new Task object whose only job is to wait through a short time and complete. I packed it and the serial read operation task together into an array, and when the
await operation completed I could see whether the read or the timeout
Task completed first.
It seemed to work, but I was unsatisfied. I felt this must be a common enough operation that there would be other options, and I was right: digging through the documentation revealed there’s a very similar-sounding
Task.WaitAny which has an overload that accepts a
TimeSpan as one of the parameters. This is a shorter version of what I did earlier, but I still had to pack the read operation into a Task array of a single element.
Two other overloads of
Task.WaitAny accepted a
CancellationToken instead, and I initially dismissed them. Creating a
CancellationTokenSource is the most flexible way to give me control over when to trigger a cancellation, but I thought that was for times when I had more sophisticated logic deciding when to cancel. Spinning up a whole separate timer callback to call
Cancel() felt like overkill.
Except it didn’t have to be that bad:
CancellationTokenSource has a constructor that accepts a count of milliseconds before canceling, so that timer mechanism was already built-in to the class. Furthermore, by using
CancellationTokenSource, I still retain the flexibility of canceling earlier than the timeout if I should choose. This felt like the best choice when I only have a single
Task at hand. I can reserve
Task.WaitAny for times when I have multiple
Task objects to coordinate. Which is also something I hope to defer until later, as I’m having a hard enough time understanding all the nuances of a single
Task in practice. Maybe some logging can help?