22

Cancel()当我通过调用my的方法取消具有以下内容的异步方法时CancellationTokenSource,它将最终停止。但是,由于该行Console.WriteLine(await reader.ReadLineAsync());需要相当长的时间才能完成,因此我尝试将我CancellationToken的 toReadLineAsync()也传递给(期望它返回一个空字符串),以使该方法对我的Cancel()调用更具响应性。但是我无法通过CancellationTokento ReadLineAsync()

我可以取消通话吗Console.WriteLine()Streamreader.ReadLineAsync()如果可以,我该怎么做?

为什么ReadLineAsync()不接受一个CancellationToken?我认为给异步方法一个可选CancellationToken参数是一个好习惯,即使该方法在被取消后仍然完成。

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

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

4

5 回答 5

12

除非可以取消,否则您无法取消该操作。你可以使用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's的实现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);
}
于 2015-02-20T10:30:04.447 回答
5

.NET 6 带来了Task.WaitAsync(CancellationToken)。所以可以写:

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

在 .NET 7(尚未发布)中,应该可以简单地编写:

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

基于https://github.com/dotnet/runtime/issues/20824https://github.com/dotnet/runtime/pull/61898

于 2021-10-07T10:29:38.663 回答
3

我将这个答案概括为:

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

于 2016-10-13T19:59:39.580 回答
1

我喜欢使用无限延迟,代码很干净。如果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;
        }
}
于 2018-07-08T13:20:22.907 回答
-3

你不能取消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);
}
于 2015-02-20T10:29:03.193 回答