Can i cancel StreamReader.ReadLineAsync with a CancellationToken?

假装没事ソ 提交于 2019-12-29 05:52:26

问题


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 formated 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).


回答1:


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);
}



回答2:


I generalized this answer to this:

public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken, Action action, bool useSynchronizationContext = true)
{
    using (cancellationToken.Register(action, useSynchronizationContext))
    {
        try
        {
            return await task;
        }
        catch (Exception ex)
        {

            if (cancellationToken.IsCancellationRequested)
            {
                // the Exception will be available as Exception.InnerException
                throw new OperationCanceledException(ex.Message, ex, cancellationToken);
            }

            // cancellation hasn't been requested, rethrow the original Exception
            throw;
        }
    }
}

Now you can use your cancellation token on any cancelable async method. For example WebRequest.GetResponseAsync:

var request = (HttpWebRequest)WebRequest.Create(url);

using (var response = await request.GetResponseAsync())
{
    . . .
}

will become:

var request = (HttpWebRequest)WebRequest.Create(url);

using (WebResponse response = await request.GetResponseAsync().WithCancellation(CancellationToken.None, request.Abort, true))
{
    . . .
}

See example http://pastebin.com/KauKE0rW




回答3:


I like to use an infinite delay, the code is quite clean. If waiting is complete WhenAny returns and the cancellationToken will throw. Else, the result of task will be returned.

public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
        using (var delayCTS = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
        {
            var waiting = Task.Delay(-1, delayCTS.Token);
            var doing = task;
            await Task.WhenAny(waiting, doing);
            delayCTS.Cancel();
            cancellationToken.ThrowIfCancellationRequested();
            return await doing;
        }
}



回答4:


You can't cancel Streamreader.ReadLineAsync(). IMHO this is because reading a single line should be very quick. But you can easily prevent the Console.WriteLine() from happening by using a separate task variable.

The check for ct.IsCancellationRequested is also redundand as ct.ThrowIfCancellationRequested() will only throw if cancellation is requested.

StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
    ct.ThrowIfCancellationRequested();
    string line = await reader.ReadLineAsync());

    ct.ThrowIfCancellationRequested();    
    Console.WriteLine(line);
}


来源:https://stackoverflow.com/questions/28626575/can-i-cancel-streamreader-readlineasync-with-a-cancellationtoken

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!