2010-05-12 3 views
14

Мне нужно передать большие файлы между компьютерами через ненадежные соединения с помощью WCF.Memorystream and Large Object Heap

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

Мои шаги:

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

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

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

Мой актуальный вопрос (ы):

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

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

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); 
} 
} 

ответ

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, то новый массив также будет выделен на кучу больших объектов, что потенциально может привести к фрагментации и, возможно, вызвать явные утечки памяти. По-моему, все еще требуется много времени, чтобы на самом деле закончилась нехватка памяти, поскольку утечка была бы довольно медленной.

Я думаю, что лучшей реализацией будет использование своего рода буферного пула для обеспечения массивов. Вы можете свернуть свои собственные (это было бы слишком сложно), но WCF предоставит вам один. Я немного переписать код, чтобы принять advatage этого:

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 для нет. байтов для записи.

Я надеюсь, что это помогает

джо

+1

Большое спасибо! Это серьезный ответ на бриллиантов. Спасибо, что указали на проблему Array.Resize. Я также никогда не слышал о BufferManager, похоже, что это поможет мне и в других областях. Это намного больше, чем я ожидал, поэтому я подумал о том, чтобы начать небольшую щедрость и дать ее вам, но я должен ждать 23 часа после начала щедрости ...Так что вам тоже нужно подождать :) – flayn

+0

Спасибо за это. Я очень рад, что могу помочь. Дайте мне знать, если что-нибудь еще. Оглядываясь назад, возможно, стоит отметить, что оптимальная реализация будет иметь один экземпляр BufferManager для всей службы. Я не знаю, насколько это практично для вас. –

+0

+1 Просто наткнулся на этого, ища ответ на подобную проблему. Никогда раньше не слышал о BufferManager - удивительный! Думаю, это будет что-то, что нужно запомнить в будущем. –

2

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

+0

Спасибо за ваше предложение, но я не хочу устанавливать IIS на каждую машину. – flayn

+2

Без проблем, просто мысль. Чтобы указать, что вам нужно только IIS на каждой машине, если они загружаются. Если клиенты загружают только с помощью BITS, то они не требуют IIS. –

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

Смотрите также RecyclableMemoryStream. От this article:

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

  • Устраните Большие распределения Heap объекта с помощью объединенных буферов
  • ПОНЕСТИ далеко меньше Gen 2 ШС, и тратить гораздо меньше времени, приостановленное из-за GC
  • утечки памяти
  • Избегайте имея ограниченный размер пула
  • фрагментации Избегайте памяти
  • Обеспечить превосходную debuggability
  • Обеспечение метрик для отслеживания эффективности