代码之家  ›  专栏  ›  技术社区  ›  flayn

内存流和大型对象堆

  •  14
  • flayn  · 技术社区  · 16 年前

    我必须使用WCF通过不可靠的连接在上的计算机之间传输大型文件。

    因为我希望能够恢复文件,并且不希望受到WCF的文件大小限制,所以我将文件分为1MB块。这些“块”以流的形式运输。到目前为止,效果还不错。

    我的步骤是:

    1. 打开文件流
    2. 将块从文件读取到byte[]并创建memoryStream
    3. 转移块
    4. 回到2。直到整个文件被发送

    我的问题在第二步。我假设当我从一个字节数组创建一个内存流时,它最终会出现在LOH上,并最终导致一个内存不足异常。实际上我不能创建这个错误,也许我的假设是错误的。

    现在,我不想发送消息中的byte[],因为wcf会告诉我数组太大。我可以更改允许的最大数组大小和/或块的大小,但我希望还有其他解决方案。

    我的实际问题:

    • 我当前的解决方案会在LOH上创建对象吗?这会给我带来问题吗?
    • 有没有更好的方法来解决这个问题?

    顺便说一句:在接收端,我简单地从到达的流中读取较小的块,并将它们直接写入文件,因此不涉及大字节数组。

    编辑:

    当前解决方案:

    for (int i = resumeChunk; i < chunks; i++)
    {
     byte[] buffer = new byte[chunkSize];
     fileStream.Position = i * chunkSize;
     int actualLength = fileStream.Read(buffer, 0, (int)chunkSize);
     Array.Resize(ref buffer, actualLength);
     using (MemoryStream stream = new MemoryStream(buffer)) 
     {
      UploadFile(stream);
     }
    }
    
    4 回复  |  直到 16 年前
        1
  •  35
  •   Joe Simmonds    16 年前

    我希望这没问题。这是我对stackoverflow的第一个回答。

    是的,如果chunkSize超过85000字节,那么数组将被分配到大对象堆上。在分配和解除分配相同大小的连续内存区域时,可能不会很快耗尽内存,因此当内存充满时,运行时可以将新块放入旧的回收内存区域。

    我会有点担心数组。调整调用的大小,因为这样会创建另一个数组(请参见 http://msdn.microsoft.com/en-us/library/1ffy6686(VS.80).aspx )如果actuallength==chunksize,这是一个不必要的步骤,因为它只适用于最后一个块。所以我至少建议:

    if (actualLength != chunkSize) Array.Resize(ref buffer, actualLength);
    

    这将删除大量的分配。如果actualsize与chunksize不同,但仍为>85000,则新数组也将分配到大型对象堆上,这可能导致该对象堆碎片化,并可能导致明显的内存泄漏。我相信,实际耗尽内存还需要很长时间,因为泄漏会很慢。

    我认为更好的实现应该是使用某种缓冲池来提供数组。你可以自己滚(这太复杂了),但WCF确实为你提供了一个。我已经稍微重写了您的代码,以便对其进行宣传:

    BufferManager bm = BufferManager.CreateBufferManager(chunkSize * 10, chunkSize);
    
    for (int i = resumeChunk; i < chunks; i++)
    {
        byte[] buffer = bm.TakeBuffer(chunkSize);
        try
        {
            fileStream.Position = i * chunkSize;
            int actualLength = fileStream.Read(buffer, 0, (int)chunkSize);
            if (actualLength == 0) break;
            //Array.Resize(ref buffer, actualLength);
            using (MemoryStream stream = new MemoryStream(buffer))
            {
                UploadFile(stream, actualLength);
            }
        }
        finally
        {
            bm.ReturnBuffer(buffer);
        }
    }
    

    这假设可以重写uploadFile的实现,以获取要写入的字节数的int。

    我希望这有帮助

    乔

        2
  •  5
  •   Manushin Igor    9 年前

    也见 RecyclableMemoryStream . 从 this article :

    Microsoft.IO.RecyclableMemoryStream 是一个内存流替换,为性能关键的系统提供卓越的行为。特别是,优化后可执行以下操作:

    • 通过使用池缓冲区消除大型对象堆分配
    • 产生的2代全球通信系统要少得多,由于全球通信系统而暂停的时间要少得多。
    • 通过限定池大小来避免内存泄漏
    • 避免内存碎片
    • 提供出色的调试能力
    • 提供绩效跟踪指标
        3
  •  2
  •   Peter Kelly    16 年前

    我不太确定你问题的第一部分,但为了更好的方式,你考虑过了吗? BITS ?它允许通过HTTP后台下载文件。您可以为它提供http://或file://uri。使用HTTP头中的range方法,可以从中断点恢复到以字节为单位下载。它由Windows Update使用。您可以订阅提供进度和完成信息的事件。

        4
  •  1
  •   flayn    14 年前

    我想出了另一个解决办法,让我知道你的想法!

    因为我不想在内存中存储大量数据,所以我正在寻找一种优雅的方法来临时存储字节数组或流。

    其思想是创建一个临时文件(您不需要特定的权限来执行此操作),然后使用它类似于内存流。将类设为一次性将在使用后清理临时文件。

    public class TempFileStream : Stream
    {
      private readonly string _filename;
      private readonly FileStream _fileStream;
    
      public TempFileStream()
      {
         this._filename = Path.GetTempFileName();
         this._fileStream = File.Open(this._filename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
      }
    
      public override bool CanRead
      {
       get
        {
        return this._fileStream.CanRead;
        }
       }
    
    // and so on with wrapping the stream to the underlying filestream
    

    …

        // finally overrride the Dispose Method and remove the temp file     
    protected override void Dispose(bool disposing)
      {
          base.Dispose(disposing);
    
      if (disposing)
      {
       this._fileStream.Close();
       this._fileStream.Dispose();
    
       try
       {
          File.Delete(this._filename);
       }
       catch (Exception)
       {
         // if something goes wrong while deleting the temp file we can ignore it.
       }
      }