C# 为什么ProtocolReader忽略CancellationToken?
我的自定义C# 为什么ProtocolReader忽略CancellationToken?,c#,asp.net-core,.net-core,kestrel-http-server,C#,Asp.net Core,.net Core,Kestrel Http Server,我的自定义ProtocolReader似乎忽略了CancellationToken,而且结果总是false 我的大部分代码来自: 我错过了什么 这是我的协议阅读器: public class CustomProtocolReader : IMessageReader<SocketObject> { public bool TryParseMessage(in ReadOnlySequence<byte> input, ref SequencePos
ProtocolReader
似乎忽略了CancellationToken
,而且结果总是false
我的大部分代码来自:
我错过了什么
这是我的协议阅读器
:
public class CustomProtocolReader : IMessageReader<SocketObject>
{
public bool TryParseMessage(in ReadOnlySequence<byte> input,
ref SequencePosition consumed,
ref SequencePosition examined,
out SocketObject message)
{
var reader = new SequenceReader<byte>(input);
var payload = input.Slice(reader.Position, input.Length);
message = new SocketObject { Buffer = payload.ToArray() };
consumed = payload.End;
examined = consumed;
return true;
}
}
while (true)
{
try
{
var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
// this should throw if nothing is received after 2 seconds.
var result = await reader.ReadAsync(protocol, cts.Token);
if (result.IsCompleted || result.IsCanceled) // this never hits
{
Console.WriteLine("Broke or cancelled");
break;
}
}
catch (OperationCanceledException) // this never gets fired
{
Console.WriteLine("Cancelled");
break;
}
finally
{
reader.Advance();
}
}
更新1:
默认的connection.Transport.Input.ReadAsync
按预期工作,并遵守CancellationToken
更新:Ohh well。。我只是忽略了。因此,是的,您应该期望在2秒钟后取消,甚至ValueTask
也应该打印IsCancled=true
:恐怕,但我不知道。我试图理解Pipe/pipewaitable
的IValueTaskSource.on完整实现,但它非常复杂。我现在没有时间
由于历史原因,我将在这里给出答案
我的自定义ProtocolReader似乎忽略了CancellationToken
事实并非如此
首先,您必须知道,CancellationToken
没有积极地执行某些操作。使用CancellationToken.CancelAfter
时,它只会通知其来源CancellationTokenSource
及其子项(由CancellationTokenSource.CreateLinkedTokenSource
创建的取消令牌源),将其IsCancellationRequested
设置为true,并触发所有处理程序(由CancellationToken.Register
)在所有涉及的CancellationTokenSource
中注册)。您可以通过不断查看IsCancellationRequested
或通过向CancellationToken.Register
注册处理程序来响应取消请求(请参见下面的示例实现)
调用reader.ReadAsync
时最深的子例程是检查一次传递给reader.ReadAsync
的令牌是否已被请求取消。让我详细说明一下:
我假设您的读取器
类型是从(如中)创建的它在内部将ConnectionContext的Transport的Input
传递给的构造函数。ConnectionContext的实现应该是。属性Transport的类型为IDuplexPipe
,在SocketConnection.StartAsync
中创建,它创建并保存其属性yTransport
到SocketConnection.Transport
(请参见的属性Transport
)Transport
的类型有Input
和Output
,并且都是类型。此类在内部用于实现System.IO.Pipelines.Pipe.ReadAsync
(请参阅类型的方法ReadAsync
),方法是调用的BeginOperation
。最后,此方法第一次调用您最初通过var result=wait reader.ReadAsync(protocol,cts.token);
传递的令牌的cancellationrequest()
因此,只有当您的取消没有被取消请求时,您的取消令牌才会被忽略
此外,结果“取消”总是错误的
您的结果不是任务。当您手动创建任务(Task.Factory.StartNew)时,您可以传递CancellationToken
。当此令牌在任务创建之前或之后收到取消请求时,Task.IsCanceled将变为true,除非已经完成(请参阅)
但您可能已经认识到,您得到的不是Task,而是ValueTask。这是一个轻量级的任务实现,可以减少任务创建开销。它不接受任何CancellationToken
。因此,您最初传递的CancellationToken
不会用于通知正在取消ValueTask
。换句话说,只要<代码>ValueTask
被创建,来自CancellationTokenSource
的取消将不再将ValueTask.IsCanceled
变为true。只有其内部保持变量以及Task或IValueTaskSource类型及其Task.IsCanceled或IValueTaskSource.GetStatus
的实现可以更改此值,但这不是e情况(见下一段)
ValueTask接受任何“预先计算”的值,即实现IValueTaskSource的任务
或“Task/”承诺。对于实现IValueTaskSource的值,指示ValueTask.IsCanceled=true
的唯一方法是当IValueTaskSource.GetStatus
返回ValueTaskSourceStatus.Canceled
时。
因此,Pipe
中的ReadAsync
返回ValueTask包装的同步结果或ValueTask包装的仍然挂起/承诺的结果,这很重要,此挂起/承诺的结果将是一个类型为Pipe
的内部变量,已在公共类Pipe。但是您最初在管道的上下文中传递的取消令牌。ReadAsync
未与管道类型的内部变量进行交互(实现IValueTaskSource
)以获取其实现方法IValueTaskSource.GetStatus
以返回ValueTaskSourceStatus.Cancelled
因此,您的任务永远不会成为真的原因是,在任何分支中,都不会创建带有初始取消令牌的任务
,也不会实现与初始传递的取消令牌交互的IValueTaskSource
实现
那么后果是什么?
当您的令牌被取消请求后调用ReadAsync时,您就没事了-您得到了所需的操作CanceledException
。之后,您就不会受到预期取消行为的影响。因此,您必须从这里开始实施您的解决方案
public Task ReadAsync() =>
// This is just an example.
Task.Delay(int.MaxValue);
public async Task DoTaskNotForever(CancellationToken token = default)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
cts.CancelAfter(2);
var tcs = new TaskCompletionSource<object>();
using var registration = cts.Token.Register(() => tcs.SetCanceled());
while (true) {
try {
await Task.WhenAny(ReadAsync(), tcs.Task).ConfigureAwait(false);
} catch (OperationCanceledException error) {
// Ensure to flush/close/reconnect socket !!! (see below why)
}
}
}
using using Nito.AsyncEx;
...
while (true)
{
try
{
var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
// this should throw if nothing is received after 2 seconds.
var result = await reader.ReadAsync(protocol, cts.Token).WaitAsync(cts.Token);
if (result.IsCompleted || result.IsCanceled) // this should hit
{
Console.WriteLine("Broke or cancelled");
break;
}
}
catch (OperationCanceledException) // should get fired after 2 seconds unless ReadAsync has not yet finished
{
Console.WriteLine("Cancelled");
break;
}
finally
{
reader.Advance();
}
}