-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add CancellationToken to StreamReader.Read* methods #20824
Comments
This raises some obvious questions... I guess I'll be the one to ask them.
|
@airbreather In regards to dotnet/corefx#2, in my opinion, it's slightly less important for the Writer side of things, since it's a less common scenario to be blocked forever on a Write that will never finish, and you now can't cancel out of. At least... as far as my imagination can take me down that route. Though it probably makes sense to add them there too, for consistency sake, and I'm sure there is some networking scenario where you are blocked on a write operation somehow that you'd want to cancel. |
We just added CancellationToken support to TextReader with our new Span APIs. dotnet/corefx#24434 @stephentoub Any comments on adding the cancellation token support for the existing methods? |
The new Memory-based overload we've added take care of ReadAsync and ReadBlockAsync; there's no need to add additional overloads for those. They don't cover ReadLineAsync or ReadToEndAsync, so we could still choose to add cancelable overloads for those. |
So rely on wrapping the array rather than add an overload for char[]? |
Right. |
any update on adding cancellation token overloads to This is a common use case for reading data from a streaming HTTP API via |
@narciero Just needs someone to drive updating the proposal. Feedback is:
Another question that should be considered is how a lower allocation version of these two APIs could/should be implemented. It might make more sense for these to return (and possibly take) |
I'd also like to see |
@JeremyKuhne If it's only ReadLine and ReadToEndAsync, this doesn't actually cover the scenario that was blocking me that caused me to open this issue. Unless ReadAsync is somehow covered by some other changes, and now has a cancellable version. |
Yeah, there's now |
That's good to hear. I should be able to rearrange my pile of inherited Streams/StreamReaders to be able to take advantage of that... but I still need the other methods. :-) |
Here's an updated proposal based on the feedback above: public abstract class TextReader
{
public virtual ValueTask<string?> ReadLineAsync(CancellationToken token);
public virtual ValueTask<string> ReadToEndAsync(CancellationToken token);
} The following changes have been made:
Any chance this might get into a review anytime soon? |
I understand that the new methods must be virtual to ensure backward compatibility, but there's just no sensible default implementation of |
A caller may have a An actual, specific, real-world use case I do not have, but it would be really awkward for all callers to have to write this: switch (someTextReader)
{
case StreamReader someStreamReader:
return await someStreamReader.ReadLineAsync(cancellationToken);
default:
return await someTextReader.ReadLineAsync();
} So writing virtual methods on |
The existing default implementations of cancelable asynchronous methods (e.g. public virtual Task<string?> ReadLineAsync(CancellationToken cancellationToken) =>
Task<string?>.Factory.StartNew(static state => ((TextReader)state!).ReadLine(), this,
cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); For reference, see the existing default implementation of For a truly asynchronous implementation,
We can come up with one easy - how about a CSV data parser? The method may be defined as While it is certainly possible to add the asynchronous overload directly to |
The changes to the classes do not look too threatening (although they haven't been tested yet), so the API review is probably a bigger blocker. |
@orthoxerox: I think there are more changes necessary StreamReader reader = ...
while (!reader.EndOfStream)
{
await reader.ReadLineAsync();
...
} A short look at the implementation of I think to be able to support cancelation following is needed: StreamReader reader = ...
while (!await reader.IsEndOfStreamAsync(cancelationToken))
{
await reader.ReadLineAsync(cancelationToken);
...
} But I'm not a expert. I just invested a view minutes for check of code. A possible alternative would be to provide and implement a own class which supports asynchron stream reading with cancelation. As somebody was asking for a usecase. Here is one: Edit: It seems to be that the Grpc team had the same problem in the past. Therefore they defined and implemented a HttpContentClientStreamReader hidden behind interface IAsyncStreamReader. Please take a look at interface documentation to see how it behaves. |
@khellang thank you for updating the proposal (#20824 (comment)) ! I don't think that Here is the proposal that I would like to present to the review board: public abstract class TextReader
{
public virtual Task<string?> ReadLineAsync()
+ public virtual ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
public virtual Task<string> ReadToEndAsync()
+ public virtual Task<string> ReadToEndAsync(CancellationToken cancellationToken)
} |
Can you please elaborate on why that should affect the decision on whether to return My intuition is that
|
@adamsitnik You seem pretty focused on a |
I think @adamsitnik's point is this API is inherently not a high-performance API. By its nature, it's allocating a new byte[] on every call (unless the source is empty). If someone cares about the extra allocation for the task, why are they using this API in the first place rather than, say, renting an array, reading into it using Stream.ReadAsync, and returning the pooled array? |
I agree; whether it returns For me, the point is to support cancellation during what could easily be a long-running task. Obviously there are more memory-efficient ways to read data than I believe these should be added to the On a side note, I also see no need to add the |
Looks good as proposed namespace System.IO
{
public partial class TextReader
{
public virtual ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken) => throw null;
public virtual Task<string> ReadToEndAsync(CancellationToken cancellationToken) => throw null;
}
} |
I'd prefer consistency when choosing between |
Consistency with what? The existing overloads are Task. |
With existing similar methods. |
|
Side note: |
Could you please create a new issue with the proposal? |
Done |
EDIT: link to updated proposal: #20824 (comment)
Today we have the following methods on StreamReader:
None of these support cancellation via CancellationTokens, even though the underlying Stream ReadAsync API's do
Proposed API
Add the following methods
Motivation
Since these methods read from an underlying stream that may have long delays before data is available, they can block for long periods of time. Especially if the underlying stream is network based.
When trying to create a responsive application in these circumstances, where long running operations that might never return can be cancelled to return control back to the user, the inability to cancel these operations in difficult.
Currently the only real option is to fork the call into a fake cancellable operation that ends up ignoring original Task when a cancellation happens, with something like ThreadingTools.WithCancellation. But that leaves the reading Task (and potentially the stream and any continuations registered on it) floating, unreferenced.
The text was updated successfully, but these errors were encountered: