不断从 NetworkStream 异步读取

本文关键字:异步 读取 NetworkStream | 更新日期: 2023-09-27 18:31:48

我是一个很新的人。NET开发人员和我目前正在阅读async/await。我需要开发一个框架,用于测试通过使用TCP远程访问服务器并从这些服务器读取/写入数据来控制的设备。这将用于单元测试。

没有应用层协议,服务器可能会根据外部事件发送数据。因此,我必须能够连续捕获来自服务器的任何数据并将其写入缓冲区,缓冲区可以从不同的上下文读取。

我的想法类似于以下片段:

// ...
private MemoryStream m_dataBuffer;
private NetworkStream m_stream;
// ...
public async void Listen()
{
  while (Connected)
  {
    try
    {
      int bytesReadable = m_dataBuffer.Capacity - (int)m_dataBuffer.Position;
      // (...) resize m_dataBuffer if necessary (...)
      m_stream.ReadTimeout = Timeout;
      lock (m_dataBuffer)
      {
        int bytesRead = await m_stream.ReadAsync(m_dataBuffer.GetBuffer(), 
          (int)m_dataBuffer.Position, bytesReadable);
        m_stream.Position += bytesRead;
      }
    }
    catch (IOException ex)
    {
      // handle read timeout.
    }
    catch (Exception)
    {
      throw new TerminalException("ReadWhileConnectedAsync() exception");
    }
  }
}

这似乎有以下缺点:

  1. 如果调用并等待Listen函数,则调用方将挂起,即使调用方必须能够继续(因为只要连接打开,就应读取网络流)。
  2. 如果将其声明为async void而不是等待它,则当任务中发生异常时,应用程序将崩溃。
  3. 如果将其声明为async Task并且不等待它,我假设也会发生同样的情况(加上我收到警告)?

以下问题随之而来:

  • 如果我不等待,是否可以捕获Listen中抛出的异常?
  • 有没有更好的方法来使用 async/await 不断从网络流中读取?
  • 尝试使用 async/await 连续读取网络流实际上是理智的,还是线程是更好的选择?

不断从 NetworkStream 异步读取

async void至少应该async Task丢弃返回值。这使得该方法符合理智的标准,并将责任推给调用者,调用者更有能力做出有关等待和错误处理的决策。

但您不必丢弃返回值。您可以附加日志记录延续:

async Task Log(Task t) {
 try { await t; }
 catch ...
}

并像这样使用它:

Log(Listen());

扔掉Log返回的任务(或者,如果您希望在逻辑上等待它),请等待它)。

或者,简单地将所有内容都包装在Listen中。情况似乎已经如此。

如果我不等待,我可以捕获 Listen 中抛出的异常吗?

您可以使用任何附加延续或同步等待的方式(后者不是您的策略)来查找异常。

有没有更好的方法使用 async/await 不断从网络流中读取?

不,这是应该做的方式。在任何给定时间都应该有一个未完成的读取 IO。(或在短时间内为零。

尝试使用 async/await 连续读取网络流实际上是理智的,还是线程是更好的选择?

两者都将正常工作。需要做出权衡。同步代码可以更简单、更易于调试,甚至更少占用 CPU。保存在线程堆栈内存和上下文切换上的异步代码。在 UI 应用中,await具有显著的优势。

我会做这样的事情:

const int MaxBufferSize = ... ;
Queue<byte> m_buffer = new Queue<byte>(MaxBufferSize);
NetworkStream m_stream = ... ;
...
// this will create a thread that reads bytes from 
// the network stream and writes them into the buffer 
Task.Run(() => ReadNetworkStream());

private static void ReadNetworkStream()
{
    while (true)
    {
        var next = m_stream.ReadByte();
        if (next < 0) break; // no more data
        while (m_buffer.Count >= maxBufferSize)
            m_buffer.Dequeue(); // drop front
        m_buffer.Enqueue((byte)next);
    }
}