【问题标题】:Most efficient way of reading a BinaryFormatter serialized object from a NetworkStream?从 NetworkStream 读取 BinaryFormatter 序列化对象的最有效方法?
【发布时间】:2012-12-22 19:14:01
【问题描述】:

我有一个应用程序通过套接字连接发送不同大小的可序列化对象,我希望它尽可能具有可扩展性。也可能有数十个甚至数百个连接。

  1. NetworkStream 来自持续侦听传入消息的 TcpClient。
  2. 我不想用标准的 NetworkStream.Read() 阻塞线程。这需要扩展。我只是假设 Read() 阻塞,因为这是这类类的标准行为,并且类上有一个 ReadTimeout 属性。
  3. 我不确定 BinaryFormatter 是只使用 Read() 还是在后台为我做了一些异步操作。我的猜测是没有。
  4. TcpClient 需要获取消息,将其读到最后,然后返回侦听消息。

所以看起来有太多方法可以给这只猫剥皮,我不确定哪种方法真正最有效。我:

只需使用 BinaryFormatter 读取 NetworkStream?

var netStream = client.GetStream();
var formatter = new BinaryFormatter();
var obj = formatter.Deserialize(netStream);

或者用新的 async/await 东西做一些魔术:

using(var ms = new MemoryStream()) 
{
   var netStream = client.GetStream();
   var buffer = new byte[1028];
   int bytesRead;
   while((bytesRead = await netStream.ReadAsync(buffer, 0, buffer.length)) > 0) {
      ms.Write(buffer, 0, buffer.Length);
   }
   var formatter = new BinaryFormatter();
   var obj = formatter.Deserialize(ms);
}

OR 与上述类似,仅利用新的 CopyToAsync 方法:

using(var ms = new MemoryStream()) 
{
   var netStream = client.GetStream();
   await netStream.CopyToAsync(ms); //4096 default buffer.
   var formatter = new BinaryFormatter();
   var obj = formatter.Deserialize(ms);
}

或者还有别的吗?

我正在寻找可提供最大可扩展性/效率的答案。

[注:以上均为伪代码,举例说明]

【问题讨论】:

  • 您为什么不尝试运行您的样本,看看哪个最有效?
  • @svick 我认为并非总是可以测试系统设计是否正确。它必须经过专家的审查。
  • @usr 是的,但这里的问题似乎不是关于正确性,而是关于效率。
  • 我真的没有一个很好的方法可以在任何实际负载下进行测试。这是我问的最大原因。我希望让一些经验丰富的兽医倾听插座的发展。我正在努力避免预先出现错误。

标签: c# .net-4.5 async-await networkstream c#-5.0


【解决方案1】:

我认为值得一提的是,从客户端的角度来看,异步与同步之间存在差异。如果你去异步......每个人通常都会经历相同的响应时间。因此,如果您的所有请求都很密集,每个人都会意识到响应时间会变慢。使用同步请求,请求简单的用户将得到更快的处理,因为他们不会被其他用户阻止。但是,如果您在同步环境中同时有许多请求,最终可能会阻塞所有线程,请求不会得到响应。

【讨论】:

    【解决方案2】:

    如果有数百个并发操作在运行,异步会更好地扩展。

    不过,连续运行会慢一些。异步具有在基准测试中很容易检测到的开销。如果您不需要选项 2,则更喜欢使用选项 1。

    【讨论】:

    • @blesh 在这种情况下非常喜欢选项(2)。你在正确的轨道上。当然,你可以使用CopyAsync
    • 我非常不同意这个答案。恰恰相反。如果您有 200 个线程,则可能不需要异步,因为如果线程被阻塞,其他线程将可用于处理请求。但是,想象一下如果您只有 1 个线程。如果该单个线程被阻塞,则 一切 都会被阻塞。学习 NodeJS。 NodeJS 是一个单线程,因此它完全异步。
    • @ScottStevens 我想你只是误解了这个答案的意思。 “数百个并发线程”并不意味着数百个 CPU 内核可用,而是意味着数百个并发操作。
    • @ScottStevens 我相信我糟糕的措辞会误导你。我在回答中将“线程”更改为“操作”。应该避免拥有数百个线程,而拥有数百个(异步)操作不是问题。尽管没有线程被消耗,但工作正在完成(或至少已排队)。
    • @usr 我明白你的意思了。我认为一般来说,在 Web 环境中,如果您希望您的应用程序具有高度可扩展性,那么在向外部资源发出请求时,最好在异步方面犯错。一般来说,我倾向于能够处理更多的并发请求,而不是对 CPU 施加一点额外压力。
    【解决方案3】:

    第一种方法在大流中存在问题。如果您要发送大数据,该代码将导致应用程序因内存不足异常而崩溃。

    第二种方法看起来非常好 - 它是异步的(意味着您不使用一些有价值的线程来等待读取完成)并且它使用数据块(这就是您应该使用流的方式)。

    所以选择第二个选项,可能稍作修改 - 一次只反序列化数据块,不要读取整个内容(除非您完全确定流长度)。

    这就是我的想法(伪代码)

    using (var networkStream = client.GetStream()) //get access to stream
    {
        while(!networkStream.EndOfStream) //still has some data
        {
            var buffer = new byte[1234]; //get a buffer
            await SourceStream.ReadAsync(result, 0, buffer); //read from network there
    
            //om nom nom buffer     
            Foo obj;
            using(var ms = new MemoryStream()) //process just one chunk
            {
                 ms.Write(buffer, 0, buffer.Length);
                 var formatter = new BinaryFormatter();
                 obj = formatter.Deserialize(ms);   //desserialise the object        
            } // dispose memory
    
            //async send obj up for further processing
        }
    }
    

    【讨论】:

    • 在一个循环中,我不断循环返回并告诉客户端等待另一条消息?
    • 我会遵循这种方法:从网络异步读取到小缓冲区,反序列化,循环和重用缓冲区。这种方法的问题是您的对象可能不是完全可构造的,即它的某些部分仍在传输中 - 这取决于对象和序列化格式(这可以用于 int 数组,但如果您传输将失败“一个 foo” 对象)。
    • 还有一个 CopyToAsync() 方法,我猜我可以使用它来处理将其传输到由 BinaryFormatter 处理的 MemoryStream。我会假设它使用适当大小的缓冲区来处理。
    • 如果使用 MemoryStream 整个数据将被缓冲。是否以块的形式读取/写入无关紧要。
    • 你真的是说让BinarayFormatter逐段读取流会导致内存问题,但是创建一个大数组(伪装成MemoryStream)不会?这对我来说没有任何意义。
    【解决方案4】:

    async/await 可以让您在等待资源时减少阻塞线程的频率,因此通常它的扩展性比线程阻塞版本更好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-31
      • 2014-12-10
      • 2015-08-11
      • 1970-01-01
      • 1970-01-01
      • 2015-01-15
      • 2022-10-23
      • 2014-03-02
      相关资源
      最近更新 更多