我有一个线程向NetworkStream写入请求。另一个线程是从这个流中读取响应。
我想让它具有容错能力。如果网络出现故障,我希望将NetworkStream替换为全新的。
我让两个线程处理IO / Socket异常。他们每个人都会尝试重新建立连接。我正在努力协调这两个线程。我不得不放置锁定部分,使代码变得相当复杂且容易出错。
有没有推荐的方法来实现这个?也许使用单个线程但是进行读或写异步?
答案 0 :(得分:1)
我发现最简单的方法是让一个线程处理协调和写入,然后使用异步处理读取。通过在CancellationTokenSource
对象中包含AsyncState
,当接收方遇到错误(调用EndRead
或流完成时),读取器代码可以发信号通知发送线程重新启动连接。协调/写入器位于创建连接的循环中,然后循环使用要发送的BlockingCollection<T>
个项目。通过使用BlockingCollection.GetConsumingEnumerable(token)
,当阅读器遇到错误时,可以取消发件人。
private class AsyncState
{
public byte[] Buffer { get; set; }
public NetworkStream NetworkStream { get; set; }
public CancellationTokenSource CancellationTokenSource { get; set; }
}
一旦你创建了连接,就可以启动异步读取过程(只要一切正常,它就会自动调用)。传递缓冲区,流和状态对象中的CancellationTokenSource
:
var buffer = new byte[1];
stream.BeginRead(buffer, 0, 1, Callback,
new AsyncState
{
Buffer = buffer,
NetworkStream = stream,
CancellationTokenSource = cts2
});
之后,您开始从输出队列中读取并写入流,直到取消或发生故障:
using (var writer = new StreamWriter(stream, Encoding.ASCII, 80, true))
{
foreach (var item in this.sendQueue.GetConsumingEnumerable(cancellationToken))
{
...
...在回调中,您可以检查失败,并在必要时点击CancellationTokenSource以通知编写器线程重新启动连接。
private void Callback(IAsyncResult ar)
{
var state = (AsyncState)ar.AsyncState;
if (ar.IsCompleted)
{
try
{
int bytesRead = state.NetworkStream.EndRead(ar);
LogState("Post read ", state.NetworkStream);
}
catch (Exception ex)
{
Log.Warn("Exception during EndRead", ex);
state.CancellationTokenSource.Cancel();
return;
}
// Deal with the character received
char c = (char)state.Buffer[0];
if (c < 0)
{
Log.Warn("c < 0, stream closing");
state.CancellationTokenSource.Cancel();
return;
}
... deal with the character here, building up a buffer and
... handing it out to the application when completed
... perhaps using Rx Subject<T> to make it easy to subscribe
... and finally ask for the next byte with the same Callback
// Launch the next reader
var buffer2 = new byte[1];
var state2 = state.WithNewBuffer(buffer2);
state.NetworkStream.BeginRead(buffer2, 0, 1, Callback, state2);