获取OutOfMemory异常线程

时间:2014-10-28 03:01:21

标签: vb.net multithreading

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

代码使用新线程将PS转换为PDF。任务大约需要半秒钟,所以对于这个测试,我只是在主线程中休息一秒钟以确保我没有运行太多任务。在抛出OutOfMemory异常之前它做了900多次。

我知道我需要使用Thread Pool,Semaphore或Task Parallel来限制我的线程,但是现在我只是在测试我的线程。

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次。承诺费从1044M上升到1272M以上。

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

3 个答案:

答案 0 :(得分:0)

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

假设ConvertPDF实现了IDisposable,这意味着在使用它之后你需要调用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

答案 1 :(得分: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。

答案 2 :(得分:0)

另一个答案是在不使用GC.Collect的情况下使用Thread.Join。这使主线程等待新线程完成。

t.Start(Params)

Params = Nothing

t.Join()

使用此方法,线程和Commit Charge略微上升,然后保持稳定。他们没有继续积累。