我可以使用CancellationToken取消StreamReader.ReadLineAsync吗?

时间:2015-02-20 10:19:53

标签: c# .net asynchronous cancellation-token

当我通过调用Cancel()的{​​{1}}方法取消包含以下内容的异步方法时,它最终会停止。但是由于行CancellationTokenSource需要花费很多时间才能完成,我尝试将我的CancellationToken传递给Console.WriteLine(await reader.ReadLineAsync());(期望它返回一个空字符串),以使该方法对我的{{ {1}}致电。但是我无法将ReadLineAsync()传递给Cancel()

我可以取消对CancellationTokenReadLineAsync()的来电,如果是,我该怎么办?

为什么Console.WriteLine()不接受Streamreader.ReadLineAsync()?我认为最好为Async方法提供一个可选的ReadLineAsync()参数,即使该方法在取消后仍然完成。

CancellationToken

更新 如下面的评论中所述,由于每行40.000个字符的输入字符串格式不正确,仅CancellationToken调用已经占用了几秒钟。打破这个问题可以解决我的响应时间问题,但是我仍然对如何取消这个长时间运行的语句的任何建议或解决方法感兴趣,如果由于某种原因打算将40.000个字符写入一行(例如将整个字符串转储到一个文件)。

4 个答案:

答案 0 :(得分:7)

除非可以取消操作,否则您无法取消操作。您可以使用WithCancellation扩展方法让代码流的行为就像它被取消一样,但底层代码仍会运行:

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

用法:

await task.WithCancellation(cancellationToken);

您无法取消Console.WriteLine并且您不需要。如果你有一个合理的string,那就很快。

关于指南:如果您的实施实际上不支持取消,那么您不应该接受令牌,因为它会发送混合消息。

如果你有一个巨大的字符串要写入控制台,你就不应该使用Console.WriteLine。您可以一次在字符中写入字符串,并且可以取消该方法:

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

    Console.WriteLine();
}

更好的解决方案是分批编写而不是单个字符。这是使用MoreLinq Batch

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

    Console.WriteLine();
}

所以,最后:

var reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
    DumpHugeString(await reader.ReadLineAsync().WithCancellation(token), token);
}

答案 1 :(得分:3)

我将此answer概括为:

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

现在,您可以在任何可取消的异步方法上使用取消令牌。例如WebRequest.GetResponseAsync:

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

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

将成为:

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

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

参见示例http://pastebin.com/KauKE0rW

答案 2 :(得分:0)

我喜欢使用无限延迟,代码很干净。 如果waiting完成,则WhenAny返回并且cancellationToken将抛出。否则,将返回task的结果。

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

答案 3 :(得分:-1)

您无法取消Streamreader.ReadLineAsync()。恕我直言,这是因为阅读单行应该非常快。但是,您可以通过使用单独的任务变量轻松阻止Console.WriteLine()发生。

ct.IsCancellationRequested的检查也是冗余的,因为ct.ThrowIfCancellationRequested()只会在请求取消时抛出。

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

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