Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I cancel StreamReader.ReadLineAsync with a CancellationToken?

When I cancel my async method with the following content by calling the Cancel() method of my CancellationTokenSource, it will stop eventually. However since the line Console.WriteLine(await reader.ReadLineAsync()); takes quite a bit to complete, I tried to pass my CancellationToken to ReadLineAsync() as well (expecting it to return an empty string) in order to make the method more responsive to my Cancel() call. However I could not pass a CancellationToken to ReadLineAsync().

Can I cancel a call to Console.WriteLine() or Streamreader.ReadLineAsync() and if so, how do I do it?

Why is ReadLineAsync() not accepting a CancellationToken? I thought it was good practice to give async methods an optional CancellationToken parameter even if the method still completes after being canceled.

StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
    if (ct.IsCancellationRequested){
        ct.ThrowIfCancellationRequested();
        break;
    }
    else
    {
        Console.WriteLine(await reader.ReadLineAsync());
    }
}

Update: Like stated in the comments below, the Console.WriteLine() call alone was already taking up several seconds due to a poorly formatted input string of 40.000 characters per line. Breaking this down solves my response-time issues, but I am still interested in any suggestions or workarounds on how to cancel this long-running statement if for some reason writing 40.000 characters into one line was intended (for example when dumping the whole string into a file).

like image 627
H W Avatar asked Feb 20 '15 10:02

H W


People also ask

How do I cancel a CancellationToken?

CancellationToken is immutable and must be canceled by calling CancellationTokenSource. cancel() on the CancellationTokenSource that creates it. It can only be canceled once. If canceled, it should not be passed to future operations.

Can a CancellationToken be reused?

Therefore, cancellation tokens cannot be reused after they have been canceled. If you require an object cancellation mechanism, you can base it on the operation cancellation mechanism by calling the CancellationToken.

How does C# CancellationToken work?

A CancellationToken enables cooperative cancellation between threads, thread pool work items, or Task objects. You create a cancellation token by instantiating a CancellationTokenSource object, which manages cancellation tokens retrieved from its CancellationTokenSource. Token property.

What is CancellationToken in .NET core?

So CancellationToken can be used to terminate a request execution at the server immediately once the request is aborted or orphan. Here we are going to see some sample code snippets about implementing a CancellationToken for Entity FrameworkCore, Dapper ORM, and HttpClient calls in Asp. NetCore MVC application.


2 Answers

You can't cancel the operation unless it's cancellable. You can use the WithCancellation extension method to have your code flow behave as if it was cancelled, but the underlying would still run:

public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken) {     return task.IsCompleted // fast-path optimization         ? task         : task.ContinueWith(             completedTask => completedTask.GetAwaiter().GetResult(),             cancellationToken,             TaskContinuationOptions.ExecuteSynchronously,             TaskScheduler.Default); } 

Usage:

await task.WithCancellation(cancellationToken); 

You can't cancel Console.WriteLine and you don't need to. It's instantaneous if you have a reasonable sized string.

About the guideline: If your implementation doesn't actually support cancellation you shouldn't be accepting a token since it sends a mixed message.

If you do have a huge string to write to the console you shouldn't use Console.WriteLine. You can write the string in a character at a time and have that method be cancellable:

public void DumpHugeString(string line, CancellationToken token) {     foreach (var character in line)     {         token.ThrowIfCancellationRequested();         Console.Write(character);     }      Console.WriteLine(); } 

An even better solution would be to write in batches instead of single characters. Here's an implementation using MoreLinq's Batch:

public void DumpHugeString(string line, CancellationToken token) {     foreach (var characterBatch in line.Batch(100))     {         token.ThrowIfCancellationRequested();         Console.Write(characterBatch.ToArray());     }      Console.WriteLine(); } 

So, in conclusion:

var reader = new StreamReader(dataStream); while (!reader.EndOfStream) {     DumpHugeString(await reader.ReadLineAsync().WithCancellation(token), token); } 
like image 120
i3arnon Avatar answered Sep 24 '22 23:09

i3arnon


.NET 6 brings Task.WaitAsync(CancellationToken). So one can write:

StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{       
    Console.WriteLine(await reader.ReadLineAsync().WaitAsync(cancellationToken).ConfigureAwait(false));
}

In .NET 7 (not yet released), it should be possible to write simply:

StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{       
    Console.WriteLine(await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
}

based on https://github.com/dotnet/runtime/issues/20824 and https://github.com/dotnet/runtime/pull/61898.

like image 25
Martin Vseticka Avatar answered Sep 25 '22 23:09

Martin Vseticka