我正在尝试用C#编写客户端以获得专有的TCP协议连接(发送/接收密钥+值对消息)。我想使用async
的{{1}} / await
功能,因此我的程序可以使用单个线程读取和写入套接字( a la JavaScript但是我对NetworkStream
的工作方式有疑问。
这是我的纲要:
NetworkStream.ReadAsync
这里有一个问题:如果public static async Task Main(String[] args)
{
using( TcpClient tcp = new TcpClient() )
{
await tcp.ConnectAsync( ... );
using( NetworkStream ns = tcp.GetStream() )
{
while( true )
{
await RunInnerAsync( ns );
}
}
}
}
private static readonly ConcurrentQueue<NameValueCollection> pendingMessagesToSend = new ConcurrentQueue<NameValueCollection>();
private static async Task RunInnerAsync( NetworkStream ns )
{
// 1. Send any pending messages.
// 2. Read any newly received messages.
// 1:
while( !pendingMessagesToSend.IsEmpty )
{
// ( foreach Message, send down the NetworkStream here )
}
// 2:
Byte[] buffer = new Byte[1024];
while( ns.DataAvailable )
{
Int32 bytesRead = await ns.ReadAsync( buffer, 0, buffer.Length );
if( bytesRead == 0 ) break;
// ( process contents of `buffer` here )
}
}
中没有要读取的数据(NetworkStream ns
),那么DataAvailable == false
中的while( true )
循环会不断运行,而CPU永远不会空闲 - 这很糟糕。
因此,如果我更改代码以删除Main
检查并且只是总是调用DataAvailable
,那么调用会有效地“阻止”直到数据可用 - 所以如果没有数据到达,那么此客户端将永远不会发送任何到远程主机的消息。所以我考虑添加500ms的超时时间:
ReadAsync
However, this does not work!显然,接受// 2:
Byte[] buffer = new Byte[1024];
while( ns.DataAvailable )
{
CancellationTokenSource cts = new CancellationTokenSource( 500 );
Task<Int32> readTask = ns.ReadAsync( buffer, 0, buffer.Length, cts.Token );
await readTask;
if( readTask.IsCancelled ) break;
// ( process contents of `buffer` here )
}
的{{1}}重载在实际请求取消时不会中止或停止,它总是忽略它(这不是一个错误?)。
我链接的质量检查表明只需关闭NetworkStream.ReadAsync
/ CancellationToken
的解决方法 - 这对我来说不合适,因为我需要保持连接活动,但只有休息一下< / em>从等待数据到达并发送一些数据。
One of the other answers建议共同等待Socket
,如下所示:
NetworkStream
...但是,这确实会阻止程序等待无限期的网络读取操作,它不会自动停止读取操作 - 所以当我的程序完成发送任何待处理的消息时,它将会第二次调用Task.Delay
,而它仍在等待数据到达 - 这意味着处理重叠IO并且不是我想要的。
I know when working with Socket
directly and its BeginReceive
/ EndReceive
methods您只是从// 2:
Byte[] buffer = new Byte[1024];
while( ns.DataAvailable )
{
Task maxReadTime = Task.Delay( 500 );
Task readTask = ns.ReadAsync( buffer, 0, buffer.Length );
await Task.WhenAny( maxReadTime, readTask );
if( maxReadTime.IsCompleted )
{
// what do I do here to cancel the still-pending ReadAsync operation?
}
}
内拨打ReadAsync
- 但是第一次如何安全地呼叫BeginReceive
,尤其是在循环中 - 应该如何使用EndReceive
API时会修改这些调用吗?
答案 0 :(得分:0)
我通过从function0()
获取Task
并将其存储在设置为NetworkStream.ReadAsync
的可变局部变量中(如果没有待处理的null
操作),否则它是一个有效的ReadAsync
实例。
不幸的是,由于Task
方法不能包含async
个参数,我需要将我的逻辑从ref
移到Main
。
这是我的解决方案:
RunInnerAsync
每当修改private static readonly ConcurrentQueue<NameValueCollection> pendingMessagesToSend = new ConcurrentQueue<NameValueCollection>();
private static TaskCompletionSource<Object> queueTcs;
public static async Task Main(String[] args)
{
using( TcpClient tcp = new TcpClient() )
{
await tcp.ConnectAsync( ... );
using( NetworkStream ns = tcp.GetStream() )
{
Task<Int32> nsReadTask = null; // <-- this!
Byte[] buffer = new Byte[1024];
while( true )
{
if( nsReadTask == null )
{
nsReadTask = ns.ReadAsync( buffer, 0, buffer.Length );
}
if( queueTcs == null ) queueTcs = new TaskCompletionSource<Object>();
Task completedTask = await Task.WhenAny( nsReadTask, queueTcs.Task );
if( completedTask == nsReadTask )
{
while( ns.DataAvailable )
{
Int32 bytesRead = await ns.ReadAsync( buffer, 0, buffer.Length );
if( bytesRead == 0 ) break;
// ( process contents of `buffer` here )
}
}
else if( completedTask == queueTcs )
{
while( !pendingMessagesToSend.IsEmpty )
{
// ( foreach Message, send down the NetworkStream here )
}
}
}
}
}
}
时,pendingMessagesToSend
都会被实例化,如果为空,并且queueTcs
被调用以取消等待SetResult(null)
行。
自从构建此解决方案后,我感觉好像没有恰当地使用Task completedTask = await Task.WhenAny( nsReadTask, queueTcs.Task );
,请参阅此问答:Is it acceptable to use TaskCompletionSource as a WaitHandle substitute?