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

当我通过调用CancellationTokenSourceCancel()方法Cancel()具有以下内容的异步方法时,它将最终停止。 但是自从Console.WriteLine(await reader.ReadLineAsync());Console.WriteLine(await reader.ReadLineAsync()); 需要花费很多时间来完成,我试图将我的CancellationToken传递给ReadLineAsync() (期望它返回一个空字符串),以使该方法对我的Cancel()调用更具响应性。 但是我无法将CancellationToken传递给ReadLineAsync()

我可以取消对Console.WriteLine()Streamreader.ReadLineAsync()的调用,如果是,我该怎么办?

为什么ReadLineAsync()不接受CancellationToken ? 我认为给Async方法一个可选的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个字符(例如将整个字符串转储到一份文件)。

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

 public static Task WithCancellation(this Task 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); } 

我将这个答案概括为:

 public static async Task WithCancellation(this Task 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

您无法取消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); } 

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

 public static async Task WithCancellation(this Task 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; } }