2014-10-28 2 views
0

저는 멀티 스레딩을 시작했습니다. 내 멀티 스레드 코드의 테스트를 실행하고 있지만 OutOfMemory 예외가 발생했습니다.OutOfMemory 예외 스레딩 받기

코드가 PS를 새로운 스레드를 사용하여 PDF로 변환 중입니다. 작업은 약 0.5 초 정도 걸리므로이 테스트에서는 주 스레드를 잠자 게 잠깐 만져서 너무 많은 작업이 실행되지 않도록합니다. 그것은 OutOfMemory 예외를 던지기 전에 900을 넘었습니다.

스레드 풀, 세마포 또는 작업 병렬을 사용하여 스레드를 제한해야한다는 것을 알고 있지만 지금은 스레드 테스트를하고 있습니다.

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 

편집 : 여기

코드의 나머지 부분입니다 내가 GroverBoy의 코드와 내 결과가 확정적이지 사이에 더 많은 테스트를했다. 가끔은 다른 쪽이 때때로 더 좋습니다. 어쩌면이 둘은 정말로 같고 그 문제는 다른 곳에서도있을 수 있습니다.

새 스레드가 완료되는 데 0.55 초가 걸리는 새 프로세스가 시작됩니다. 메인 쓰레드가 매 반복마다 1 초를 기다린다면, 한번에 하나 이상의 쓰레드 나 하나의 열린 파일을 가질 수 없다는 것을 의미합니다. 왜 이것이 사실이 아닌가?

실제로 일어나는 일은 다양하며 그 이유는 확실하지 않습니다. 나는 메인 루프에서 100 초의 루프와 1 초의 대기를 테스트하고있다. 필자는 일반적으로 작업 관리자의 성능 탭을 봅니다. 때로는 코드를 실행하고 스레드 수는 2 ~ 6 여분으로 변동하고 커밋 요금은 1044M에서 1150M 사이에서 변동합니다. 이것이 내가 원하는거야.

다른 경우 동일한 코드 (100 회 반복)를 실행하고 스레드 수가 63 개를 초과하여 계속 증가합니다. 그리고 Commit Charge는 1044M에서 1272M 이상으로 계속 증가합니다.

프로그램이 스레드를 일관되게 정리할 수 있도록하려면 어떻게해야합니까?

+1

'callConvertToPDF'가 완료되면 새 스레드가 종료되고 정리됩니다. 'callConvertToPDF' 안에서 스트림을 풀어 주나요? PS 또는 PDF 파일은 변환이 완료된 후에도 열린 상태로 유지됩니다. – kennyzx

+0

@kennyzx 귀하의 의견을 보내 주셔서 감사합니다. 호기심에 대비하여 나머지 코드를 게시했지만 GroverBoy의 대답이 그 차이를 만들었습니다. 감사. –

+1

이 코드를 올바르게 이해하면 900 개 이상의 파일 이름을 읽었으며 각 파일에 대해 새 스레드와 새 프로세스를 모두 시작합니다. 그 맞습니까? – Enigmativity

답변

0

또 다른 대답은 GC.Collect를 사용하지 않고 Thread.Join을 사용하는 것입니다. 이렇게하면 새 스레드가 완료 될 때까지 주 스레드가 대기합니다.

이 방법을 사용하면 스레드와 Commit Charge가 약간 증가한 다음 안정적으로 유지됩니다. 그들은 계속 축적하지 않았다.

+1

이것은 한 번에 2 개의 스레드 (메인과 작업자) 만 사용하는 접근 방식처럼 보입니다. 아마도 이것은 무제한 쓰레드로 얻을 수있는 10 배의 속도 향상을 가져 오지 않을까요? 그 사이에 뭔가를 찾고 싶습니다 : 몇 가지 최적의 숫자 N> 1 작업자 스레드에서 스레드 수를 조절하는 솔루션. 스레드 + 프로세스 수와 메모리 사용량 간의 관계를 살펴봄으로써 실험을 통해 N을 결정할 수 있습니다. 이것은 다른 기계와 비트율에 따라 달라 지므로 작은 일도 없습니다. 아마도 누군가 (TPL 팀은?) 아마도 N을 계산하기위한 휴리스틱을 문서화했다. – groverboy

+1

이러한 리소스는 도움이 될 수 있습니다. [ "메모리 부족"으로 인해 실제 메모리가 참조되지 않음] (http://blogs.msdn.com/b/ericlippert/archive/2009/06/08/out-of-memory- 메모리를 프로파일 링하기 위해 [Managed Code에서 메모리 누수를 식별하고 방지] (http://msdn.microsoft.com/en-us/magazine/cc163491)를 참조하십시오. aspx). – groverboy

0

ConvertPDF의 인스턴스를 900 개 (또는 무엇이든) 만들지 만 절대로 파기하지 않으므로 코드에서 OutOfMemoryException이 발생한다는 사실을 추측합니다. 물론 다른 코드 (보이지 않음)가 문제의 원인 일 수 있습니다. 어쨌든 여기에 ...

ConvertPDF가 IDisposable을 구현한다고 가정 해 봅시다. 즉, ConvertPDF를 사용하면 ConvertPDF.Dispose를 호출하거나 Using 절의 ConvertPDF를 사용하여 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() 

코드 this page에서 Excel을 해제하는 데 사용되는 것과 동일합니다.

규칙 # 1이 GC.Collect를 사용하지 않는다는 것을 깨닫습니다. 거기에 더 좋은 대답이 있습니까?

이 메서드를 사용하면 스레드가 누적되지 않고 커밋 커밋이 증가하지 않았습니다. 나는 이것으로 메모리 부족 예외를 얻지 않을 것이다. 그러나 나는 더 좋은 대답을 듣게되어 기쁘다. 나는 실제 생산 코드에서 Thread.Sleep을 사용하고 싶지 않다.