两个NetworkStream上的ReadAsync() - 我使用" await"是否正确?

时间:2015-03-02 13:44:13

标签: c# sockets asynchronous async-await tcpclient

我使用两个ReadAsync()调用,Task.WhenAny()来处理两个NetworkStream(TcpClient)。

以下等待代码是否会错过任何数据捕获?

  • 代码中的Q1 :如果两个流同时具有新数据,会发生什么情况?
  • 代码中的Q2 :WriteAsync()是否有可能耗费太长时间而丢失存储缓冲区?
  • Q3 :是否有更好的"解决这个问题的方法

我正在编写一段代码,旨在充当TCP流的中间人过滤器(稍后允许过滤/监控某些数据包)

广义逻辑应该是:

  • 客户端建立与过滤器的连接,然后过滤器建立与所选服务器的新连接
  • 如果有任何数据从客户端到达,请保存并将其发送到服务器
  • 如果有任何数据从服务器到达,请将其保存并发送给客户端

存在错误处理(列出的地方)..我错过了什么重要的事吗?


我使用以下answer to a question about ".Net 4.5 Async Feature for Socket Programming"作为起点:

var read_task_from_client = rx_stream.ReadAsync(rx_buffer, 0, ActiveBufferSize);
var read_task_from_server = tx_stream.ReadAsync(tx_buffer, 0, ActiveBufferSize);

try
{
  while (true)
  {
     Task<int> read_task_occurred;
     try
     {
        read_task_occurred = await Task.WhenAny(read_task_from_client, read_task_from_server);
            //Q1: What happens if both streams have new data at EXACTLY the same time?

        if (read_task_occurred.Status != TaskStatus.RanToCompletion)
        {
          Trace.WriteLine(string.Format("[{0}] - Task failure", ID, read_task_occurred.ToString()));
          break;
        }
     }
     catch (AggregateException aex)
     {
        for (int i = 0; i < aex.Data.Values.Count; i++)
        {
          var aex_item = aex.Data[i];
          Trace.WriteLine(string.Format("[{0}] - Aggregate failure {1} - {2}", ID, i, aex_item));
        }
        break;
     }

     var bytes_read = read_task_occurred.Result;
     if (read_task_occurred.Result == 0)
     {
        // If a read-operation returns zero, the stream has closed.
        OneStreamHasClosed(read_task_from_client, read_task_from_server, read_task_occurred);
        break;
     }

     if (read_task_occurred == read_task_from_client)
     {
        BytesFromClient += read_task_from_client.Result;
        Trace.WriteLine(string.Format("[{0}] - Client-to-Server: {1}", ID, bytes_read));
        await tx_stream.WriteAsync(rx_buffer, 0, bytes_read);
        await FileStream_Incoming.WriteAsync(rx_buffer, 0, bytes_read);
            // Q2: Any chance of the WriteAsync taking too long?
            //    (e.g. rx_buffer begins to be filled again before being written to tx_stream or the filestream)

        read_task_from_client = rx_stream.ReadAsync(rx_buffer, 0, ActiveBufferSize);
     }
     else if (read_task_occurred == read_task_from_server)
     {
        BytesFromServer += read_task_from_server.Result;
        Trace.WriteLine(string.Format("[{0}] - Server-to-Client: {1}", ID, bytes_read));
        await rx_stream.WriteAsync(tx_buffer, 0, bytes_read);
        await FileStream_Outgoing.WriteAsync(tx_buffer, 0, bytes_read);

        read_task_from_server = tx_stream.ReadAsync(tx_buffer, 0, ActiveBufferSize);
     }
  }
}
finally
{
  FileStream_Incoming.Close();
  FileStream_Outgoing.Close();
}

到目前为止,这似乎按预期工作,捕获并记录多个流...但是,我不确定我是否使用等待语句安全

稍后将在多个线程中运行(可能每个Incoming-Connection一个,但这是一个单独的主题)

更新(代码中的Q2

通过重构原始&#34;等待tx_stream.Write ...&#34;和#34;等待xxx_FileStream.Write ...&#34;如下,我相信我能够在第二季度提高一个主要的竞争条件..仍然不确定这是否是#34;最佳/推荐&#34;溶液:

// Code changed to a call to MultiWrite
private void MultiWrite(byte[] buffer, int bytes_read, Stream s1, Stream s2)
{
  Task writer1 = s1.WriteAsync(buffer, 0, bytes_read);
  Task writer2 = s2.WriteAsync(buffer, 0, bytes_read);
  Task.WaitAll(writer1, writer2);
}

更新2(等待的代码测试)

我被告知等待不允许并发任务运行...这让我感到困惑,因为我无法理解以下内容如何/为什么会运行......

private async Task<char> SimpleTask(char x, int sleep_ms) { return await Task.Run(() => { Console.Write(x); Thread.Sleep(sleep_ms); return x; }); }
internal async void DoStuff()
{
  var a_task = SimpleTask('a', 100);
  var b_task = SimpleTask('b', 250);
  var c_task = SimpleTask('c', 333);

  while (true)
  {
    var write_task_occurred = await Task.WhenAny(a_task, b_task, c_task);
    var char_written = write_task_occurred.Result;
    switch (char_written)
    {
      case 'a': a_task = SimpleTask('a', 100); break;
      case 'b': b_task = SimpleTask('b', 250); break;
      case 'c': c_task = SimpleTask('c', 333); break;
    }
  }
}

上面的代码段确实运行了(正如我所料,会产生以下多线程废话:

  

aabacabaacabaacbaaabcaabacaabacabaabacaabacabaacabaacbaabacaabacabaacabaabacaab

任何人都可以解释上述方法错误的位置/原因,如果是,可以如何改进。


更新3:将逻辑拆分为两种方法

我已将&#34;写入输出流和文件,确保两个输出都包含“缓冲区”中的数据。在进一步Read()&#34;之前并根据我之前对Q2的更新来分割代码以调用MultiWrite()

根据@usr和@Pekka的建议,我将代码拆分为以下两种方法......

private void ProcessStreams_Good()
{
  Task t1 = CopyClientToServer(), t2 = CopyServerToClient();

  Trace.WriteLine(string.Format("[{0}] - Data stats: C={1}, S={2}", ID, BytesFromClient, BytesFromServer));
  Trace.WriteLine(string.Format("[{0}] - connection closed from {1}", ID, Incoming.Client.RemoteEndPoint));
}
private async void ProcessStreams_Broken()
{
  await CopyClientToServer(); await CopyServerToClient();

  Trace.WriteLine(string.Format("[{0}] - Data stats: C={1}, S={2}\r\n", ID, BytesFromClient, BytesFromServer));
  Trace.WriteLine(string.Format("[{0}] - connection closed from {1}", ID, Incoming.Client.RemoteEndPoint));
}

private async Task CopyClientToServer()
{
  var bytes_read = await rx_stream.ReadAsync(rx_buffer, 0, ActiveBufferSize);
  while (bytes_read > 0)
  {
    BytesFromClient += bytes_read; Trace.WriteLine(string.Format("[{0}] - Client-to-Server: {1}", ID, bytes_read));
    MultiWrite(rx_buffer, bytes_read, tx_stream, FileStream_FromClient);
    bytes_read = await rx_stream.ReadAsync(rx_buffer, 0, ActiveBufferSize);
  }
}
private async Task CopyServerToClient()
{
  var bytes_read = await tx_stream.ReadAsync(tx_buffer, 0, ActiveBufferSize);
  while (bytes_read > 0)
  {
    BytesFromClient += bytes_read; Trace.WriteLine(string.Format("[{0}] - Server-to-Client: {1}", ID, bytes_read));
    MultiWrite(tx_buffer, bytes_read, rx_stream, FileStream_FromServer);
    bytes_read = await tx_stream.ReadAsync(tx_buffer, 0, ActiveBufferSize);
  }
}

是的,我知道ProcessStreams_Broken()失败并且ProcessStreams_Good()按预期工作的原因。

问:这个新代码稍微整洁一点,但它是否更好&#34;更好&#34; ?


延迟更新(问题结束后)

问题结束后,我遇到了一个非常有用的Best Practices for async/await链接。

2 个答案:

答案 0 :(得分:1)

awaitWhenAny不会启动任何操作。他们只是等待正在运行的操作完成。您已启动的所有读取将最终完成,数据将从流中获取。无论您是否观察到结果,都是如此。

我知道您希望将数据从客户端转发到服务器以及从服务器转发到客户端。那么为什么不同时启动两个异步方法,每个方法都执行两个中继方向之一?这消除了WhenAny和所有复杂逻辑的需要。你需要扔掉它。

  

代码中的Q1:如果两个流在同一时间都有新数据会发生什么?

您不需要该问题的答案。无论何时完成,您都必须处理所有读取的完成。否则,你会丢失数据。也许你假设非完整的优秀读物被(某种程度上)取消了,实际上只有一次阅读&#34;服用&#34;?!事实并非如此。所有阅读完成。没有办法取消一个(不丢弃数据)。

  

代码中的Q2:WriteAsync()是否有可能花费太长时间并丢失存储缓冲区?

不确定你的意思。如果发生超时,您需要一个策略来处理它。通常,您需要记录错误并关闭。

答案 1 :(得分:1)

并发就是非决定论。通道的两个端点必须具有单独的时钟,并且无法分辨您首先收到的消息(在时钟抖动内)。如果您(以及整个OS堆栈)公平地对接收到的消息进行操作并转发它们,那么发生这种情况的顺序就不相关了。

如果你想避免任何偏见,那么就要形成一种将任何偏好引入任何一个方向的情况。例如,您的测试Task.WhenAny(read_task_from_client, read_task_from_server);可能偏向其中一项任务。使用@usr的建议来创建单独的方法来避免这种情况。

最后,在拆除会话时要非常小心。无法准确模拟所有可能的情况,以突然从其中一个端点可能执行的用户代码中拆除。您的仿真保真度将受到此挑战,并可能导致结果无效。同样,当另一方丢弃会话时,您可能已接受一个流上的数据。没有办法正确地从中恢复 - 你能做的最好的事情就是假装伙伴在他们看到这个之前就放弃了他们的结局。