spe*_*der 4 c# tcplistener stream cancellation async-await
请考虑以下简化示例(准备在LinqPad中滚动,需要提升帐户):
void Main()
{
Go();
Thread.Sleep(100000);
}
async void Go()
{
TcpListener listener = new TcpListener(IPAddress.Any, 6666);
try
{
cts.Token.Register(() => Console.WriteLine("Token was canceled"));
listener.Start();
using(TcpClient client = await listener.AcceptTcpClientAsync()
.ConfigureAwait(false))
using(var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
var stream=client.GetStream();
var buffer=new byte[64];
try
{
var amtRead = await stream.ReadAsync(buffer,
0,
buffer.Length,
cts.Token);
Console.WriteLine("finished");
}
catch(TaskCanceledException)
{
Console.WriteLine("boom");
}
}
}
finally
{
listener.Stop();
}
}
Run Code Online (Sandbox Code Playgroud)
如果我连接一个telnet客户端localhost:6666并且在5秒钟内无所事事,为什么我会看到"令牌被取消"但从未看到"繁荣"(或"完成")?
这个NetworkStream会不会取消取消吗?
我可以结合使用Task.Delay()和解决这个问题Task.WhenAny,但是我希望能够按预期工作.
相反,以下取消示例:
async void Go(CancellationToken ct)
{
using(var cts=new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
try
{
await Task.Delay(TimeSpan.FromSeconds(10),cts.Token)
.ConfigureAwait(false);
}
catch(TaskCanceledException)
{
Console.WriteLine("boom");
}
}
}
Run Code Online (Sandbox Code Playgroud)
像预期的那样打印"热潮".这是怎么回事?
Ste*_*ary 13
不,NetworkStream不支持取消.
不幸的是,底层的Win32 API并不总是支持每次操作取消.传统上,您可以取消特定句柄的所有 I/O,但取消单个 I/O操作的方法是最近的.大多数.NET BCL是针对XP API(或更旧版本)编写的,但不包括CancelIoEx.
Stream通过"伪造"对取消(以及异步I/O)的支持来解决这个问题,即使实现不支持它也是如此.取消的"假"支持只是立即检查令牌,然后启动无法取消的常规异步读取.这就是你所看到的NetworkStream.
对于套接字(以及大多数Win32类型),传统方法是在要中止通信时关闭句柄.这会导致所有当前操作(读取和写入)失败.从技术上讲,这是违反BCL线程安全的记录,但确实有效.
cts.Token.Register(() => client.Close());
...
catch (ObjectDisposedException)
Run Code Online (Sandbox Code Playgroud)
另一方面,如果您想要检测半开场景(您的一方正在阅读但另一方已失去其连接),那么最佳解决方案是定期发送数据.我在博客上更多地描述了这一点.
| 归档时间: |
|
| 查看次数: |
2746 次 |
| 最近记录: |