runtime: Developers can await a Task with a timeout
A very popular StackOverflow Q&A asks how to await a Task
with a timeout. A simple Task.WithTimeout(TimeSpan)
method would alleviate a lot of time developers spend looking for how to properly do this, and avoid doing it wrong (e.g. leaving timers for finalization).
See how we offer a WithTimeout
extension method from vs-threading.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 15
- Comments: 35 (33 by maintainers)
I like the
ConfigureAwait
overload approach, since it more clearly communicates that it is not the underlying task that is being cancelled, but it only concerns this particular awaiter.Thinking about deadlines is interesting. But I’d rather see a single helper added somewhere, e.g. along the lines of:
rather than add yet another set of overloads to every method throughout the system that takes a timeout. That would be a separate proposal.
Sort of: it has that API, but it does something different.
It’s there because the
GetAsyncEnumerator
interface accepts aCancellationToken
, but if you’re using the languageawait foreach
support, you’re not explicitly callingGetAsyncEnumerator
, rather code generated for you is doing so… so this extension method exists to let you pass a token through. And while it could have just been calledConfigureAwait
, it doesn’t because it’s not just about addingConfigureAwait
onto awaits, but rather what arguments are passed toGetAsyncEnumerator
.As such, it’s then different from what’s being discussed in this issue: it’s not the equivalent of using the proposed
WithCancellation
on each await. If, for example, an iterator ignored its[EnumeratorCancellation]
argument, theCancellationToken
supplied to theWithCancellation
would be a nop. In contrast, the one in this proposal would actually cancel the await even as the underlying operation represented by the task may still be in flight.I think there are several questions that need to be answered before considering a concrete API.
One is key use cases. One of the reasons we added ConfigureAwait as we did was the idea that we might in the future add additional overloads for things exactly like cancellation and timeouts, e.g.
There are pros and cons to this, in particular on the plus side is we don’t actually need to allocate a Task and are free to do whatever we can internally to make this as efficient as possible (and it arguably makes the semantics clearer, that it’s specifically about the await operation rather than about the task itself). The primary con is it’s not as composable: if you get back a Task, you can use it as you would any other task, in many other situations than you could the awaitable struct returned from ConfigureAwait.
Regarding ValueTask, it’s a middle ground: it gives us a bit more flexibility in how we might optimize the implementation, at the expense of usability; if we expect the overwhelming use case to be
await task.WithWhatever
, then ValueTask could be the right choice instead of (not in addition to) Task. If we expect it to be something else, then Task is the right answer.There’s also the question of having both timeouts and cancellation tokens that you want to use together, i.e. should these really be separately named methods, one for cancellation and one for timeout, or should a single API (maybe with overloads) let you utilize both at the same time, ala Task.Delay(int, cancellationToken).
I haven’t included
ValueTask
overloads, since it should be straightforward to convert them to task instances.Eirik, let me know if you decide to modify CA2016’s behavior to exclude ConfigureAwait. I wrote it and I’ll be happy to help.
Here’s what an API for cancellable task awaitables could look like (wlog for the case of
Task<T>
):@stephentoub suggested we incorporate the AwaitBehavior enum proposal into this design. It should be possible to embed it into the existing
ConfiguredTaskAwaitable
struct without any breaking changes, so assuming we go ahead with it we should also include the following APIs:If we are in agreement, I can create a new issue containing an API proposal based on the above sketch, and we can probably close this issue and #22144.
Let’s keep such a proposal separate. I don’t think it should impact this.
Example
WithTimeout
,WithCancellation
. And a third variation that is used to reduce the number of exceptions thrown -WhenCancelled
, related API proposal #37505, example use case:These implementations involve additional exception throwing/catching because they are built using async/await, but it would be possible to implement them more efficiently so that no exceptions are thrown inside the implementation (only in the consuming code if it uses
await
).It should be fairly trivial to write such extension methods when an equivalent
ConfigureAwait
overload is available.What is the policy with respect to specifying the timeout as
int millisecondsTimeout
or as aTimeSpan
? It seems to me that if .NET could be done “greenfield” there only should beTimeSpan
and never an int. But there’s an argument for keeping consistency with existing APIs.Has it been decided on how new APIs with a timeout should be designed?
My opinion: Something like
await Task.Delay(100)
is an anti-pattern. All code should use theTimeSpan
form. New APIs should only useTimeSpan
because getting rid of that unfortunate timeout style is more important than keeping consistency.So maybe the
int millisecondsTimeout
overloads should be removed from this proposal.