当我通过调用Cancel()
的CancellationTokenSource
方法取消具有以下内容的异步方法时,它最终会停止。然而,由于行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<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
将返回并且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;
}
}
你无法取消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);
}