.net 是否可以使用CancellationToken取消StreamReader.ReadLineAsync?

ckocjqey  于 2023-01-10  发布在  .NET
关注(0)|答案(5)|浏览(105)

当我调用我的CancellationTokenSourceCancel()方法取消我的async方法时,它最终会停止,但是由于Console.WriteLine(await reader.ReadLineAsync());行需要花费相当多的时间才能完成。我也尝试将我的CancellationToken传递给ReadLineAsync()(期望它返回一个空字符串),以便使方法更好地响应我的Cancel()调用。然而,我无法将CancellationToken传递给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());
    }
}
    • 更新**:正如下面的注解所述,由于每行40.000个字符的输入字符串格式很差,仅Console.WriteLine()调用就已经占用了几秒钟的时间。但我仍然对任何建议或工作区感兴趣,如果出于某种原因写入40,则如何取消这个长时间运行的语句。000个字符到一行中(例如,将整个字符串转储到文件中时)。
7uzetpgm

7uzetpgm1#

.NET 6带来了Task.WaitAsync(CancellationToken)。因此,我们可以这样写:

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

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

using 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

eulz3vhy

eulz3vhy2#

除非操作是可取消的,否则无法取消操作。可以使用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();
}

一个更好的解决方案是批量写入而不是单个字符。下面是一个使用MoreLinqBatch的实现:

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

pxy2qtax3#

我将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

lymnna71

lymnna714#

我喜欢使用无限延迟,代码很干净。如果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;
        }
}
gzjq41n4

gzjq41n45#

你不能取消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);
}

相关问题