我有一个多线程通过单个套接字发送数据的场景。已在消息中插入唯一ID,并在响应消息中回显唯一ID。当套接字被隔离到单个客户端时(正如预期的那样),一切都很有效。现在,我正在为多个线程寻找异步/等待模式,客户端等待特定的响应。
要演示的一些代码:
using (var ns = new NetworkStream(_socket))
{
byte[] requestBuffer = GetBuffer(request);
await ns.WriteAsync(requestBuffer, 0, request.Length);
byte[] responseBuffer = await ReceiveMessageAsync(ns);
// process response message
}
上面的示例在多线程场景中不起作用,因为消息可以以任何顺序返回,因此线路上的下一条消息可能不属于当前客户端。我的想法是客户端将使用其唯一ID(将其存储在字典中)来注册委托或任务,并且当带有该唯一ID的消息返回时,委托或任务将被完成'响应字节。我猜这个使用EventHandler很容易实现,但我正在寻找一种等待响应的方法。
例如:
using (var ns = new CustomNetworkStream(_socket))
{
Task waitTask = ns.RegisterTask(this.UniqueId);
byte[] requestBuffer = GetBuffer(request);
await ns.WriteAsync(requestBuffer, 0, request.Length);
byte[] responseBuffer = await waitTask;
// process response message
}
我不知道" RegisterTask"方法看起来如何,如何存储任务,如何使任务等待'然后完成'以任务为结果。
有什么想法吗?我已经研究过Toub的异步协调基元系列,但我不确定这是否是正确的方法。
答案 0 :(得分:3)
您需要的是TaskCompletionSource<byte[]>
用作同步构造,ConcurrentDictionary
用于在ID和TCS以及侦听器之间进行映射:
ConcurrentDictionary<UniqueId, TaskCompletionSource<byte[]>> _dictionary;
async Task Listen(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
using (var ns = new NetworkStream(_socket))
{
byte[] responseBuffer = await ReceiveMessageAsync(ns);
var id = ExtractId(responseBuffer);
TaskCompletionSource<byte[]> tcs;
if (dictionary.TryRemove(id, out tcs))
{
tcs.SetResult(responseBuffer);
}
else
{
// error
}
}
}
}
Task RegisterTask(UniqueId id)
{
var tcs = new TaskCompletionSource<byte[]>();
if (!_dictionary.TryAdd(id, tcs))
{
// error
}
return tcs.Task;
}
然而,正如Stephen Cleary建议的那样,您可能希望使用现有的解决方案。
答案 1 :(得分:3)
因此,您需要将所有这些内容包装到一个新类中,因为您需要在您阅读的地点和您所写的地点之间共享状态。
每次去写入流时,您都需要接受唯一ID,并在查找中添加一个条目,将id映射到TaskCompletionSource
。然后,write方法可以从该TCS返回Task
。
然后,您可以拥有一个单独的阅读器,它只是从您的流中读取,找到与该响应的ID关联的词典条目,并设置其结果。
public class MyNetworkStream : IDisposable
{
private NetworkStream stream;
private ConcurrentDictionary<int, TaskCompletionSource<byte[]>> lookup =
new ConcurrentDictionary<int, TaskCompletionSource<byte[]>>();
private CancellationTokenSource disposalCTS = new CancellationTokenSource();
public MyNetworkStream(Socket socket)
{
stream = new NetworkStream(socket);
KeepReading();
}
public void Dispose()
{
disposalCTS.Cancel();
stream.Dispose();
}
public Task<byte[]> WriteAndWaitAsync(byte[] buffer, int offset,
int count, int uniqueID)
{
var tcs = lookup.GetOrAdd(uniqueID, new TaskCompletionSource<byte[]>());
stream.WriteAsync(buffer, offset, count);
return tcs.Task;
}
private async void KeepReading()
{
try
{
//TODO figure out what you want for a buffer size so that you can
//read a block of the appropriate size.
byte[] buffer = null;
while (!disposalCTS.IsCancellationRequested)
{
//TODO edit offset and count appropriately
await stream.ReadAsync(buffer, 0, 0, disposalCTS.Token);
int id = GetUniqueIdFromBlock(buffer);
TaskCompletionSource<byte[]> tcs;
if (lookup.TryRemove(id, out tcs))
tcs.TrySetResult(buffer);
else
{
//TODO figure out what to do here
}
}
}
catch (Exception e)
{
foreach (var tcs in lookup.Values)
tcs.TrySetException(e);
Dispose();
//TODO consider any other necessary cleanup
}
}
private int GetUniqueIdFromBlock(byte[] buffer)
{
throw new NotImplementedException();
}
}