我可以使用 CancellationToken 取消 StreamReader.ReadLineAsync 吗?
Can I cancel StreamReader.ReadLineAsync with a CancellationToken?
当我通过调用 CancellationTokenSource
的 Cancel()
方法取消具有以下内容的异步方法时,它最终会停止。然而,由于行 Console.WriteLine(await reader.ReadLineAsync());
需要相当多的时间才能完成,我试图将我的 CancellationToken
也传递给 ReadLineAsync()
(期望它传递给 return 一个空字符串)以便使该方法对我的 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()
调用就已经占用了几秒钟。打破这一点解决了我的响应时间问题,但我仍然对任何关于如何取消这个 long-运行 语句的建议或解决方法感兴趣,如果出于某种原因将 40.000 个字符写入一行(例如在转储时整个字符串到一个文件中)。
您无法取消 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);
}
您无法取消操作,除非它是可取消的。您可以使用 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);
}
我将此 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))
{
. . .
}
我喜欢用无限延迟,代码很干净。
如果 waiting
完成 WhenAny
returns 并且 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;
}
}
.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/20824 and https://github.com/dotnet/runtime/pull/61898。
当我通过调用 CancellationTokenSource
的 Cancel()
方法取消具有以下内容的异步方法时,它最终会停止。然而,由于行 Console.WriteLine(await reader.ReadLineAsync());
需要相当多的时间才能完成,我试图将我的 CancellationToken
也传递给 ReadLineAsync()
(期望它传递给 return 一个空字符串)以便使该方法对我的 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()
调用就已经占用了几秒钟。打破这一点解决了我的响应时间问题,但我仍然对任何关于如何取消这个 long-运行 语句的建议或解决方法感兴趣,如果出于某种原因将 40.000 个字符写入一行(例如在转储时整个字符串到一个文件中)。
您无法取消 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);
}
您无法取消操作,除非它是可取消的。您可以使用 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);
}
我将此 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))
{
. . .
}
我喜欢用无限延迟,代码很干净。
如果 waiting
完成 WhenAny
returns 并且 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;
}
}
.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/20824 and https://github.com/dotnet/runtime/pull/61898。