我刚刚开始使用多线程。我正在运行我的多线程代码的测试,但我得到一个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以上。
我该怎么做才能确保程序能够一致地清理线程?
答案 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略微上升,然后保持稳定。他们没有继续积累。