2014-10-28 7 views
0

Я только начинаю с многопоточности. Я выполняю проверку моего многопоточного кода, но получаю исключение OutOfMemory.Получение потока исключения OutOfMemory

Код преобразует PS в PDF с использованием новой темы. Задача занимает около полутора секунд, поэтому для этого теста я просто сплю главный поток на секунду, чтобы убедиться, что у меня не слишком много задач. Он сделал более 900, прежде чем выбросить исключение OutOfMemory.

Я знаю, что мне нужно использовать пул потоков, семафор или задачу. Параллельно ограничить мои потоки, но пока я просто проверяю свои темы.

Dim sr As New StreamReader(PSTempFolder & "PDFWrite.txt") 

Do While Not sr.EndOfStream 

    'get PS 
    Dim FileNamePS As String = sr.ReadLine 

    'get folder 
    Dim CustFolder As IO.DirectoryInfo 
    CustFolder = GetCustFolder(FileNamePS) 

    'set PDF path and name 
    FileNamePDF = CustFolder.FullName & "\Statement.pdf" 

    Dim t As Thread 
    Dim n As ConvertPDF = Nothing 
    n = New ConvertPDF 
    n.DeletePS = False 
    n.PSFileName = FileNamePS 
    n.PDFFileName = FileNamePDF 

    t = New Thread(AddressOf n.callConvertToPDF) 
    t.Start() 

    'wait 
    Thread.Sleep (1000) 

Loop 

sr.Close() 

Кажется, он должен создавать слишком много потоков, а не очищать старые. Как очистить/удалить поток перед созданием нового?

Я полагаю, что второе решение (в этом контексте) будет просто использовать тот же поток (я думаю, что я могу это сделать), но по этому вопросу меня больше интересует избавление от потока и освобождение памяти. Как мне это сделать?

Вот остальная часть кода:

Class ConvertPDF 

    Public PSFileName As String 
    Public PDFFileName As String 
    Public DeletePS As Boolean = False 

    Delegate Function ConvertToPDFdel(ByVal svPsFileName As String, _ 
        ByVal svPDFName As String, _ 
        ByVal DeletePS As Boolean) As Integer 

    Sub callConvertToPDF() 
     Dim dlgt As New ConvertToPDFdel(AddressOf ConvertToPDF) 
     Dim i As Integer = dlgt.Invoke(PSFileName, PDFFileName, DeletePS) 
    End Sub 

End Class 

Public Function ConvertToPDF(ByVal svPsFileName As String, _ 
          ByVal svPDFName As String, _ 
          ByVal DeletePS As Boolean) As Integer 

    'check for file 
    If Not IO.File.Exists(svPsFileName) Then 
     Throw New ApplicationException(svPsFileName & " cannot be found") 
    End If 

    'delete old file 
    If IO.File.Exists(svPDFName) Then IO.File.Delete(svPDFName) 

    'convert 
    Dim myProcInfo As New ProcessStartInfo 
    myProcInfo.FileName = DanBSolutionsLocation & "Misc\GhostScript\GSWIN32C.EXE" 
    myProcInfo.Arguments = "-sDEVICE=pdfwrite -q -dSAFER -dNOPAUSE -sOUTPUTFILE=""" & svPDFName & """ -dBATCH """ & svPsFileName & """" 
    'Debug.Print(myProcInfo.Arguments) 

    'do the conversion 
    Dim myProc As Process = Process.Start(myProcInfo) 

    'wait for finish (no more than 20 seconds) 
    myProc.WaitForExit(20000) 

    myProcInfo = Nothing 
    myProc.Dispose() 

    'delete PS 
    If DeletePS Then 
     If IO.File.Exists(svPDFName) Then IO.File.Delete(svPsFileName) 
    End If 

End Function 

EDIT: Я сделал еще несколько тестов между кодом и моим GroverBoy и результаты не являются окончательными. Иногда лучше иногда другой. Возможно, эти два на самом деле одинаковы, и проблема в другом месте.

Новый поток запускает новый процесс, который занимает 0,55 секунды. Если основной поток ожидает 1 секунду каждой итерации, это должно означать, что у нас никогда не будет более одного потока или одного открытого файла за раз. Почему это не так?

Что на самом деле происходит, будет отличаться, и я не знаю, почему. Я тестирую с циклом 100 и 1 секундой ожидания основного потока. Обычно я смотрю вкладку «Производительность» Диспетчера задач. Иногда я запускаю код, и количество потоков будет колебаться между 2-6 дополнительными, а Commit Charge будет колебаться от 1044M до 1150M. Это то, чего я хочу.

В других случаях я запускаю один и тот же код (100 итераций), а количество потоков продолжает увеличиваться до более чем 63 дополнительных. И Commit Charge продолжает расти с 1044M до более чем 1272M.

Что я могу сделать, чтобы программа автоматически очищала потоки?

+1

Если 'callConvertToPDF' запускается до завершения, новый поток будет выходить и очищаться. Вы выпускаете потоки внутри 'callConvertToPDF'? Я думаю, что файлы PS или PDF остаются открытыми после завершения преобразования. – kennyzx

+0

@kennyzx Спасибо за ваш комментарий, который был полезен. Я отправил остальную часть кода на случай, если вам интересно, но ответ GroverBoy изменил ситуацию. Благодарю. –

+1

Если я правильно понимаю этот код, вы читаете более 900 имен файлов, и вы начинаете как новый поток, так и новый процесс для каждого файла. Это верно? – Enigmativity

ответ

0

Другой ответ заключается в использовании Thread.Join без использования GC.Collect. Это удерживает основной поток в ожидании завершения нового потока.

t.Start(Params) 

Params = Nothing 

t.Join() 

Используя этот метод, потоки и фиксация заряда немного выросли, а затем остались устойчивыми. Они не продолжали накапливаться.

+1

Это похоже на подход, который использует только два потока за раз: главный и рабочий. Предположительно, это не дает десятикратного ускорения, которое вы получаете с неограниченными потоками? Я хотел бы найти что-то среднее между ними: решение, которое дросселирует количество потоков при некотором оптимальном количестве N> 1 рабочего потока. Вы можете определить N посредством экспериментов, глядя на соотношение между потоком и количеством процессов и использованием памяти. Это будет различаться на разных машинах и для разной битности, так что это небольшая работа. Возможно, кто-то (команда TPL?) Уже задокументировал эвристику для расчета N. – groverboy

+1

Вы можете найти эти полезные ресурсы: [«Из памяти» не относится к физической памяти] (http://blogs.msdn.com/b/ericlippert/archive/2009/06/08/out-of-memory- не-refer-to-physical-memory.aspx) и для профилирования памяти [Идентифицировать и предотвращать утечки памяти в управляемом коде] (http://msdn.microsoft.com/en-us/magazine/cc163491. ASPX). – groverboy

0

Я делаю дикое предположение, что ваш код вызывает OutOfMemoryException, потому что он создает, но не уничтожает 900 экземпляров ConvertPDF. Конечно, возможно, что некоторые другие из вашего кода (не показаны) создают проблему. В любом случае, здесь идет ...

Предположим, что ConvertPDF реализует IDisposable, а это значит, что после его использования вам нужно вызвать ConvertPDF.Dispose или, лучше, использовать ConvertPDF в предложении Using, чтобы вызвать Dispose автоматически. Ваш код не структурирован, чтобы сделать это в подходящее время, потому что он не знает, когда завершил выполнение callConvertToPDF. Вы можете реструктурировать так, чтобы рабочий поток также выполнял работу по инициализации и удалению экземпляра ConvertPDF.

В приведенном ниже коде добавлены вспомогательные классы Paths, которые будут использоваться как параметр для рабочего потока. Предупреждение: Я действительно не развиваюсь в VB.NET, так что это может не скомпилировать :)

Class Paths 
    Public FileNamePS As String 
    Public FileNamePDF As String 
End Class 

Sub Main() 
    Using sr As New StreamReader(PSTempFolder & "PDFWrite.txt") 
     Do While Not sr.EndOfStream 
      Dim MyPaths As Paths = New Paths() 

      'get PS 
      MyPaths.FileNamePS = sr.ReadLine 

      'get folder 
      Dim CustFolder As IO.DirectoryInfo = GetCustFolder(MyPaths.FileNamePS) 

      'set PDF path and name 
      MyPaths.FileNamePDF = IO.Path.Combine(CustFolder.FullName, "Statement.pdf") 

      Dim t As Thread = New Thread(AddressOf ConvertPStoPdf) 

      ' start the thread, passing the parameter that ConvertPStoPdf will need 
      t.Start(MyPaths) 

      'wait 
      Thread.Sleep (1000) 
     Loop 
    End Using ' automatically disposes StreamReader 
End Sub 

Sub ConvertPStoPdf(Data As Object) 
    ' get Paths instance from weak-typed parameter 
    Dim MyPaths As Paths = CType(Data, Paths) 

    Using C As ConvertPDF = New ConvertPDF   
     C.DeletePS = False 
     C.PSFileName = MyPaths.FileNamePS 
     C.PDFFileName = MyPaths.FileNamePDF 
     C.callConvertToPDF    
    End Using ' automatically disposes ConvertPDF 
End Sub 
+0

@D_Bester - Рад помочь. Теперь, когда вы разместили код для ConvertPDF, я вижу, что он не реализует IDisposable, т. Е. Компилятор не разрешит его в качестве аргумента для оператора 'Using'. Итак, каким образом этот ответ помог? – groverboy

+0

После вашего комментария я решил провести несколько бок о бок. Результаты неубедительны. Иногда лучше иногда другой. Возможно, эти два на самом деле одинаковы, и проблема в другом месте. –

0

Я нашел один ответ, чтобы восстановить память, используя GC.Collect. Rico's blog: When to call GC.Collect()

t.Start (Params) 

Params = Nothing 

Thread.Sleep (1000) 

GC.Collect() 
GC.WaitForPendingFinalizers() 
GC.Collect() 
GC.WaitForPendingFinalizers() 

код такой же, как то, что используется, чтобы освободить Excel от this page.

Я понимаю правило № 1 не использует GC.Collect. Так есть лучший ответ?

С помощью этого метода нити не накапливались, и Commit Charge не поднимался. Я не буду получать исключение из памяти с этим. Но я был бы рад услышать лучший ответ. Я действительно не хочу использовать Thread.Sleep в производственном коде.

Смежные вопросы