16

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

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

我的步骤是:

  1. 打开文件流
  2. 将文件中的块读入 byte[] 并创建 memorystream
  3. 转移块
  4. 回到 2. 直到整个文件被发送

我的问题出在第 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

4 回答 4

39

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

是的,如果您的块大小超过 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,那么新数组也将被分配到大对象堆上,这可能会导致它碎片化并可能导致明显的内存泄漏。我相信仍然需要很长时间才能真正耗尽内存,因为泄漏会非常慢。

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

我希望这有帮助

于 2010-05-18T13:29:26.757 回答
8

另请参阅RecyclableMemoryStream。从这篇文章

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

  • 使用池化缓冲区消除大对象堆分配
  • 产生更少的第 2 代 GC,并且由于 GC 而暂停的时间也更少
  • 通过限制池大小来避免内存泄漏
  • 避免内存碎片
  • 提供出色的可调试性
  • 提供性能跟踪指标
于 2017-03-04T17:53:06.503 回答
2

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

于 2010-05-12T13:31:45.950 回答
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.
   }
  }
于 2012-07-03T08:39:56.983 回答