2014-10-28 154 views
0

我刚刚开始使用多线程。我正在运行我的多线程代码的测试,但我得到一个OutOfMemory异常。获取OutOfMemory异常线程

代码使用新线程将PS转换为PDF。这项任务大约需要半秒钟的时间,所以对于这个测试,我只是简单地休息一秒钟,以确保我没有太多的任务运行。它在抛出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个额外之间波动,并且Commit Charge将在1044M到1150M之间波动。这就是我要的。其他时间我运行相同的代码(100次迭代),并且线程数量不断上升到63以上。 Commit Charge持续从10.44M增长到超过1272M。

我该怎么做才能确保程序能够一致地清理线程?

+1

如果'callConvertToPDF'运行完成,新线程将退出并清理。你在'callConvertToPDF'里释放流吗?我想PS或PDF文件在转换完成后保持打开状态。 – kennyzx 2014-10-28 04:29:55

+0

@kennyzx感谢您的评论,这是有帮助的。我发布了其他代码以防万一,但GroverBoy的答案造成了不同。谢谢。 – 2014-10-29 01:57:15

+1

如果我正确理解此代码,您正在读取超过900个文件名,并且您为每个文件启动一个新线程和一个新进程。那是对的吗? – Enigmativity 2014-10-29 02:21:05

回答

0

另一个答案是使用Thread.Join而不使用GC.Collect。这使主线程等待直到新线程完成。

t.Start(Params) 

Params = Nothing 

t.Join() 

使用此方法,线程和提交费用上涨了一点,然后保持稳定。他们没有继续积累。

+1

这看起来像一次只使用两个线程的方法:main和worker。大概这不会产生无限线程的十倍加速?我会寻找一些介于两者之间的东西:一个解决方案,它可以在最佳数量N> 1的工作线程中对线程数进行限制。您可以通过实验确定N,查看线程+进程数和内存使用情况之间的关系。这将在不同的机器和不同的位数上有所不同,所以没有小的工作。可能有人(TPL团队?)已经记录了启发式计算N. – groverboy 2014-10-31 02:11:54

+1

您可能会发现这些资源有用:[“内存不足”不参考物理内存](http://blogs.msdn.com/b/ericlippert/archive/2009/06/08/out-of-memory-不要参考物理内存.aspx),并且,对于内存分析,[识别并防止托管代码中的内存泄漏](http://msdn.microsoft.com/zh-cn/magazine/cc163491。 ASPX)。 – groverboy 2014-10-31 02:12:38

0

我在猜测你的代码会导致OutOfMemoryException,因为它会创建但不会破坏ConvertPDF的900(或其他)实例。当然,您的其他代码(未显示)可能会导致问题。无论如何去...

让我们假设ConvertPDF实现IDisposable,这意味着使用它后,您需要调用ConvertPDF.Dispose或更好地使用Using子句中的ConvertPDF自动调用Dispose。您的代码没有被构造为在适当的时候这样做,因为它无法知道callConvertToPDF何时完成执行。您可以重组,以便工作线程也可以完成初始化和处理ConvertPDF实例的工作。

下面的代码添加了一个助手类路径,作为工作线程的参数。 警告:我真的没有在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 2014-10-29 10:56:37

+0

您的评论后,我决定做一些更多的并行测试。结果是不确定的。有时候一个人有时候会好一些。也许这两个真的是一样的,问题在别处。 – 2014-10-30 03:33:59

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。