What To Do When Await Waits Too Long
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.WhenAny
or 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?
[This Hello3DP
programming exercise is publicly available on GitHub]