内存流和大对象堆

时间:2010-05-12 13:15:37

标签: c# wcf memory out-of-memory large-object-heap

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

因为我希望能够恢复该文件,并且我不想被WCF限制在我的文件大小中,所以我将这些文件分块为1MB。这些“块”以流的形式传输。到目前为止,哪个效果很好。

我的步骤是:

  1. 打开文件流
  2. 从文件中读取块到byte []并创建内存流
  3. 转移块
  4. 返回2.直到整个文件发送
  5. 我的问题在第2步。我假设当我从字节数组创建一个内存流时,它将最终在LOH上并最终导致outofmemory异常。我实际上无法创建此错误,也许我的假设是错误的。

    现在,我不想在消息中发送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 个答案:

答案 0 :(得分:35)

我希望这没关系。这是我在StackOverflow上的第一个答案。

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

我会对Array.Resize调用有点担心,因为这会创建另一个数组(参见http://msdn.microsoft.com/en-us/library/1ffy6686(VS.80).aspx)。如果actualLength == Chunksize,这是一个不必要的步骤,因为除了最后一个块之外,它将是所有的。所以我至少建议:

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

这应该删除大量的分配。如果actualSize与chunkSize不同但仍然是> 85000然后新的数组也将在Large对象堆上分配,可能导致它碎片并可能导致明显的内存泄漏。我相信它仍然需要很长时间才能实现内存不足,因为泄漏会很慢。

我认为更好的实现方法是使用某种缓冲池来提供数组。你可以自己动手(这太复杂了)但是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的实现以获取no的int。要写的字节数。

我希望这会有所帮助

答案 1 :(得分:5)

另见RecyclableMemoryStream。 来自this article

Microsoft.IO.RecyclableMemoryStream是一个MemoryStream替代品,可为性能关键系统提供卓越的性能。特别是它被优化以执行以下操作:

  • 使用池化缓冲区消除大对象堆分配
  • 远远少于第2代GC,并且因GC而花费的停顿时间要少得多。
  • 通过限制池大小来避免内存泄漏
  • 避免内存碎片
  • 提供出色的可调试性
  • 提供效果跟踪指标

答案 2 :(得分:2)

我对你问题的第一部分不太确定,但至于更好的方法 - 你考虑过BITS吗?它允许通过http下载文件的后台。您可以提供http://或file:// URI。它可以从中断的角度恢复,并使用http HEADER中的RANGE方法以字节块的形式下载。它由Windows Update使用。您可以订阅提供有关进度和完成信息的事件。

答案 3 :(得分:1)

我已经为此提出了另一个解决方案,让我知道你的想法!

由于我不想在内存中包含大量数据,因此我一直在寻找一种优雅的方法来临时存储字节数组或流。

我们的想法是创建一个临时文件(您不需要特定的权限来执行此操作),然后使用它类似于内存流。使类Disposable将在使用后清除临时文件。

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.
   }
  }