C#如何在重新连接NetworkStream时协调读写线程?

时间:2015-09-07 04:41:42

标签: c# multithreading networkstream

我有一个线程向NetworkStream写入请求。另一个线程是从这个流中读取响应。

我想让它具有容错能力。如果网络出现故障,我希望将NetworkStream替换为全新的。

我让两个线程处理IO / Socket异常。他们每个人都会尝试重新建立连接。我正在努力协调这两个线程。我不得不放置锁定部分,使代码变得相当复杂且容易出错。

有没有推荐的方法来实现这个?也许使用单个线程但是进行读或写异步?

1 个答案:

答案 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);