Поток памяти и куча больших объектов - PullRequest
14 голосов
/ 12 мая 2010

Мне приходится передавать большие файлы между компьютерами через ненадежные соединения с использованием WCF.

Поскольку я хочу иметь возможность возобновить файл и не хочу, чтобы размер моего файла ограничивался WCF, я делю файлы на части по 1 МБ. Эти «чанки» транспортируются как поток. Который пока работает довольно хорошо.

Мои шаги:

  1. открыть поток файлов
  2. чтение фрагмента из файла в байт [] и создание потока памяти
  3. блок передачи
  4. вернуться к 2. пока весь файл не будет отправлен

Моя проблема в шаге 2. Я предполагаю, что когда я создаю поток памяти из байтового массива, он заканчивается в LOH и в конечном итоге вызывает исключение вне памяти. Я не мог создать эту ошибку, возможно, я ошибаюсь в своем предположении.

Теперь я не хочу отправлять байт [] в сообщении, так как WCF скажет мне, что размер массива слишком велик. Я могу изменить максимально допустимый размер массива и / или размер моего чанка, но я надеюсь, что есть другое решение.

Мой фактический вопрос (ы):

  • Будет ли мое текущее решение создавать объекты на LOH, и это вызовет у меня проблемы?
  • Есть ли лучший способ решить эту проблему?

Кстати: на принимающей стороне я просто читаю меньшие порции из прибывающего потока и записываю их непосредственно в файл, так что большие байтовые массивы не используются.

Edit:

текущее решение:

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 ]

35 голосов
/ 18 мая 2010

Я надеюсь, что все в порядке. Это мой первый ответ на 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. байтов для записи.

Надеюсь, это поможет

Джо

5 голосов
/ 04 марта 2017

См. Также RecyclableMemoryStream . С этой статьи :

Microsoft.IO.RecyclableMemoryStream - это замена MemoryStream, которая обеспечивает превосходное поведение для систем, критичных к производительности. В частности, он оптимизирован для выполнения следующих действий:

  • Устранить выделения кучи больших объектов с помощью объединенных буферов
  • Получите гораздо меньше GC второго поколения и тратите гораздо меньше времени на паузу из-за GC
  • Избегайте утечек памяти, имея ограниченный размер пула
  • Избегать фрагментации памяти
  • Обеспечить отличную отладку
  • Предоставление метрик для отслеживания производительности
2 голосов
/ 12 мая 2010

Я не очень уверен насчет первой части вашего вопроса, но что касается лучшего способа - вы рассматривали БИТЫ ? Это позволяет фоновую загрузку файлов через http. Вы можете предоставить ему http: // или file: // URI. Он возобновляется с того момента, как был прерван и загружается кусками байтов, используя метод RANGE в http HEADER. Он используется Центром обновления Windows. Вы можете подписаться на события, которые предоставляют информацию о ходе и завершении.

1 голос
/ 03 июля 2012

Я предложил другое решение для этого, дайте мне знать, что вы думаете!

Поскольку я не хочу, чтобы в памяти было большое количество данных, я искал элегантный способ временного хранения.хранить байтовые массивы или поток.

Идея состоит в том, чтобы создать временный файл (для этого вам не нужны особые права), а затем использовать его подобно потоку памяти.Создание класса 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.
   }
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...