【问题标题】:Call Stack size?调用堆栈大小?
【发布时间】:2017-06-26 02:30:32
【问题描述】:

来自密歇根的问候,

我有一个连续运行的程序(记录数据),它在一个 Sub(“记录数据”Sub)内的 While 循环内运行,然后当日志文件“满”时,它跳转到另一个 Sub 以创建一个新文件,然后跳回到“记录数据”子继续。无论如何,它会一直这样做,并且可以像这样运行数小时,创建 100 多个文件或更多数据。问题是程序在某些时候崩溃并且它总是在程序的这一部分内崩溃(这两个子之一,虽然我没有确定哪一个。当我在部署程序的机器上运行调试器时,调用堆栈相当大。我想知道这是否是一个问题,以及如何管理。这可能是程序崩溃的原因(调用堆栈变得太大?)。我遇到了某种内存异常至少在一次崩溃中出错。我昨天对代码进行了一些编辑以尝试缓解这种情况。我遇到的最后一次崩溃(今天早上当我进入办公室时)是一个空引用异常错误,尽管我不能除非我在调试模式下从我的开发机器上运行程序,否则我计划在哪里运行程序,以准确捕捉这两个 Subs 中发生崩溃的代码行。我需要像我一样在一夜之间运行它说,程序可以在崩溃 oc 之前运行几个小时诅咒。无论如何,问题在于调用堆栈。大型调用堆栈有问题吗?这是如何管理/清除的?

谢谢, D

Public Sub dataAcquiring()
    'Receive the collection of channels in this sub and append data to each of the channels
    'Set up the channel group
    Dim message1 As String = "The data file may have been deleted or moved prior to a new data file and directory being created.  Check the main 'Data' directory and be sure the file exists, or simply create a new data file."
    Dim testBool As Boolean = False

    'Set the global variable to True if running the application from the development machine in debug mode.  Otherwise, initialize it to false for deployment.
    If Connectlocal = True Then
        statsFile = "C:\Users\dmckin01\Desktop\Data\" & folderName & "\" & dataFileName & "_stats.csv"
    Else : statsFile = "D:\Data\" & folderName & "\" & dataFileName & "_stats.csv"
    End If

    Try
        logFile.Open()
    Catch ex As Exception
        MessageBox.Show(Me, ex.Message & message1, "File not found", MessageBoxButtons.OK, MessageBoxIcon.Error)
        cbRecord.Checked = False
        Return
    End Try

    Dim i As Integer = 0, n As Integer = 0, hvar As Integer, value As Single, count As Integer = 0, maxValue As Single
    Dim b As Boolean = False, returnValue As Type, stringVar As String, lastValidNumber As Integer
    Dim dtype As System.Type
    Dim channelGroupName As String = "Main Group"
    Dim channelGroup As TdmsChannelGroup = New TdmsChannelGroup(channelGroupName)
    Dim channelGroups As TdmsChannelGroupCollection = logFile.GetChannelGroups()

    If (channelGroups.Contains(channelGroupName)) Then
        channelGroup = channelGroups(channelGroupName)
    Else
        channelGroups.Add(channelGroup)
    End If

    'Set up the TDMS channels
    Dim Names As String() = New String(13) {" Spindle Speed (rpm) ", " Oil Flow (ccm) ", " Torque (Nm) ", " LVDT Displacement (mm) ", " Linear Pot Displacement (mm) ", _
                                                     " Pneu. Actuator (0=OFF, 1=ON) ", " Elec. Actuator (0=OFF, 1=ON) ", " Hydr. Actuator (0=OFF, 1=ON) ", _
                                                     " Upper Tank Oil Temp. (°F) ", " Lower Tank Oil Temp. (°F) ", " Exit Oil Temp. (°F) ", _
                                                     " Inlet Oil Temp. (°F) ", " Part Temp. (°F) ", " Time Stamp "}
    Dim dataArrayNames As String() = New String(13) {"arrSpeed", "arrFlow", "arrTorque", "arrLVDT", "arrLinPot", "arrActPneu", "arrActElec", "arrActHydr", _
                                                     "arrUpperOil", "arrLowerOil", "arrExitOil", "arrInletOil", "arrTestPart", "arrTimeStamp"}

    Dim OPCTagNames As String() = New String(13) {"peakTorque", "peakTorqueSpeed", "peakTorquePlatePos", "timeToPeakTorque", "firstPeakTorque", "firstPeakTorqueSpeed", _
                                                "firstPeakTorquePlatePos", "timeToFirstPeakTorque", "peakDecel", "peakJerk", "engagementSpeed", "slidePlateSpeed", _
                                                "timeOfEngagement", "totalEnergy"}


    Dim bools As Boolean() = New Boolean(13) {recSpeed, recOilFlow, recTorque, recLVDT, recLinPot, recActPneu, recActElec, recActHydr, recUpperOil, recLowerOil, _
                                              recExitOil, recInletOil, recTestPart, recTimeStamp}

    'Instantiate the TDMS channels to be used.  We have to do this each and every time this Sub is executed because National Instruments
    'does not have a method to 'clear' the channel group.
    Dim ch0 As TdmsChannel = New TdmsChannel(Names(0), TdmsDataType.Float)   'spindle speed
    Dim ch1 As TdmsChannel = New TdmsChannel(Names(1), TdmsDataType.Float)   'oil flow
    Dim ch2 As TdmsChannel = New TdmsChannel(Names(2), TdmsDataType.Float)   'torque
    Dim ch3 As TdmsChannel = New TdmsChannel(Names(3), TdmsDataType.Float)   'actuator position (LVDT)
    Dim ch4 As TdmsChannel = New TdmsChannel(Names(4), TdmsDataType.Float)   'actuator position (LINEAR POT)
    Dim ch5 As TdmsChannel = New TdmsChannel(Names(5), TdmsDataType.Float) 'actuator state (pneu)
    Dim ch6 As TdmsChannel = New TdmsChannel(Names(6), TdmsDataType.Float) 'actuator state (elec)
    Dim ch7 As TdmsChannel = New TdmsChannel(Names(7), TdmsDataType.Float) 'actuator state (hydr)
    Dim ch8 As TdmsChannel = New TdmsChannel(Names(8), TdmsDataType.Float)  'upper oil tank temp
    Dim ch9 As TdmsChannel = New TdmsChannel(Names(9), TdmsDataType.Float)  'lower oil tank temp
    Dim ch10 As TdmsChannel = New TdmsChannel(Names(10), TdmsDataType.Float) 'Exit oil tank temp
    Dim ch11 As TdmsChannel = New TdmsChannel(Names(11), TdmsDataType.Float) 'Inlet oil temp
    Dim ch12 As TdmsChannel = New TdmsChannel(Names(12), TdmsDataType.Float) 'Part temp
    Dim ch13 As TdmsChannel = New TdmsChannel(Names(13), TdmsDataType.String) 'Time stamp
    Dim Channels As TdmsChannelCollection
    Dim chans As TdmsChannel() = New TdmsChannel(13) {ch0, ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8, ch9, ch10, ch11, ch12, ch13}
    Channels = channelGroup.GetChannels()

    ch0.UnitString = "RPM" : ch0.Description = "Rotational speed of the spindle shaft."
    ch1.UnitString = "CCM" : ch1.Description = "Oil flow from the specimen pump."
    ch2.UnitString = "Nm" : ch2.Description = "Torque from the torque cell."
    ch3.UnitString = "mm" : ch3.Description = "Linear displacement of the linear velocity displacement transducer."
    ch4.UnitString = "mm" : ch4.Description = "Linear displacement of the linear potentiometer."
    ch5.UnitString = "BIT" : ch5.Description = "Binary state of the pneumatic actuator (0=OFF, 1=ON)."
    ch6.UnitString = "BIT" : ch6.Description = "Binary state of the electric actuator (0=OFF, 1=ON)."
    ch7.UnitString = "BIT" : ch7.Description = "Binary state of the hydraulic actuator (0=OFF, 1=ON)."
    ch8.UnitString = "°F" : ch8.Description = "Upper tubular tank oil temperature."
    ch9.UnitString = "°F" : ch9.Description = "Lower (main) tank oil temperature."
    ch10.UnitString = "°F" : ch10.Description = "Thermocouple (Location: Remote rack, EL3318, Ch.2)."
    ch11.UnitString = "°F" : ch11.Description = "Thermocouple (Location: Remote rack, EL3318, Ch.3)."
    ch12.UnitString = "°F" : ch12.Description = "Thermocouple (Location: Remote rack, EL3318, Ch.1)"
    ch13.UnitString = "nS" : ch13.Description = "Time when the data was captured."

    'The only TDMS channels that get added to the collection are the ones that the user selects on the 'Configure Data File' form.
    'That is what this If-Then block is for. 
    If Channels.Count = 0 Then
        jArray.Clear()
        plcArrayNames.Clear()
        For Each [boolean] In bools
            If [boolean] = True Then
                Channels.Add(chans(i))
                Channels = channelGroup.GetChannels 'new
                jArray.Add(jaggedarray(i))
                plcArrayNames.Add(dataArrayNames(i))
            End If
            i += 1
        Next
    End If

    'At this point, we are ready to write data to the TDMS file.
    'Establish the line of communication to the PLC so we can read the data arrays.
    Dim tcClient As New TwinCAT.Ads.TcAdsClient()
    Dim dataStreamRead As TwinCAT.Ads.AdsStream = New AdsStream
    Dim binaryReader As System.IO.BinaryReader = New BinaryReader(dataStreamRead)

    If Connectlocal = True Then
        tcClient.Connect(851) 'local
    Else : tcClient.Connect(AMSNetID, 851)
    End If

    While cbRecord.Checked = True
        b = tcClient2.ReadAny(DRHvar, GetType(Boolean)) 'read the handshaking variable from the PLC
        If b = False Then
            'This For loop reads the appropriate arrays in the PLC and then writes that data to the appropriate arrays here.
            'The arrays in here will eventually get written to the TDMS file.
            i = 0
            n = 0
            writingData = True
            For Each [string] In dataArrayNames
                If dataArrayNames(n) = plcArrayNames(i) Then
                    hvar = tcClient.CreateVariableHandle("IO_HS.Data." & dataArrayNames(n))
                    value = 0
                    returnValue = jArray(i).GetType
                    If returnValue.Name = "Single[]" Then
                        dataStreamRead.SetLength(jArray(0).Length * 4)
                        dataStreamRead.Position = 0
                        tcClient.Read(hvar, dataStreamRead)
                        For Each [element] In jArray(0)
                            jArray(i)(value) = binaryReader.ReadSingle()
                            value += 1
                        Next
                    ElseIf returnValue.Name = "Int64[]" Then
                        dataStreamRead.SetLength(jArray(0).Length * 8)
                        dataStreamRead.Position = 0
                        tcClient.Read(hvar, dataStreamRead)
                        For Each [element] In jArray(0)
                            jArray(i)(value) = binaryReader.ReadInt64()
                            value += 1
                        Next
                    ElseIf returnValue.Name = "String[]" Then
                        dataStreamRead.SetLength(jArray(0).Length * 32)
                        dataStreamRead.Position = 0
                        tcClient.Read(hvar, dataStreamRead)
                        For Each [element] In jArray(0)
                            stringVar = binaryReader.ReadChars(32)
                            lastValidNumber = Math.Max(Math.Max(Math.Max(Math.Max(Math.Max(Math.Max(Math.Max(Math.Max(Math.Max(stringVar.LastIndexOf("0"), stringVar.LastIndexOf("1")), stringVar.LastIndexOf("2")), stringVar.LastIndexOf("3")), stringVar.LastIndexOf("4")), stringVar.LastIndexOf("5")), stringVar.LastIndexOf("6")), stringVar.LastIndexOf("7")), stringVar.LastIndexOf("8")), stringVar.LastIndexOf("9"))
                            If lastValidNumber > 0 Then
                                jArray(i)(value) = stringVar.Substring(0, lastValidNumber + 1)
                            Else
                                jArray(i)(value) = "Invalid Timestamp"
                            End If
                            value += 1
                        Next
                    End If
                    tcClient.DeleteVariableHandle(hvar)
                    i += 1
                    If i = plcArrayNames.Count Then
                        Exit For
                    End If
                End If
                n += 1
            Next

            'This For loop appends/writes the data from each array to the actual TDMS file.
            i = 0
            For Each [array] In jArray
                dtype = Channels(i).GetDataType
                If dtype.Name = "Int32" Then
                    Channels(i).AppendData(Of Integer)(jArray(i))
                ElseIf dtype.Name = "Single" Then
                    Channels(i).AppendData(Of Single)(jArray(i))
                ElseIf dtype.Name = "Boolean" Then
                    Channels(i).AppendData(Of Boolean)(jArray(i))
                ElseIf dtype.Name = "String" Then
                    Channels(i).AppendData(Of String)(jArray(i))
                End If
                i += 1
            Next

            Try
                'Call the DataAnalyzer dll to write stats of the cycle to stats CSV file.  Also plot the data of the cycle on the chart on the UI
                Invoke(Sub() DataAnalyzer.Analyze(arrSpeed, arrTorque, arrLinPot))
                Invoke(Sub() plotData())
                Invoke(Sub() DataAnalyzer.WriteData(statsFile, logFile.Path, arrTimeStamp(0), plcData.cyclesCompleted))
            Catch ex As Exception
                testBool = True
            End Try

            'Populate the datagridview cells with the data values
            dgvStats.Item(1, 0).Value = DataAnalyzer.peakTorque
            dgvStats.Item(1, 1).Value = DataAnalyzer.engagementSpeed
            dgvStats.Item(1, 2).Value = DataAnalyzer.slidePlateSpeed
            dgvStats.Item(1, 3).Value = plcData.bimbaTravSpeed
            dgvStats.Item(1, 4).Value = plcData.lastCycleTime
            dgvStats.Item(1, 5).Value = plcData.currentCycleTime
            dgvStats.Item(1, 6).Value = plcData.meanCycleTime
            dgvStats.Item(1, 7).Value = plcData.cyclesPerHr

            'NEW CODE to Evalute the elements in the arrTorque array to get the Max value recorded
            maxValue = 0
            For Each [element] In arrTorque
                maxValue = Math.Max(maxValue, element)
            Next
            If maxValue <= plcData.torqueAlrmSP And plcData.cycleStarted Then
                torqueLowCount += 1
            Else : torqueLowCount = 0
            End If

            'Let the PLC know that we received the data and are now ready for the next set (handshaking variable).
            tcClient2.WriteAny(DRHvar, True)
        End If

        'If the data count in the first column of the TDMS file exceeds the number here, then
        'close the file and create a new one, then continue to append/write data
        If Channels(0).DataCount >= 1020000 Then
            For Each channel As TdmsChannel In chans
                channel.Dispose() : channel = Nothing
            Next
            chans = Nothing
            channelGroup.Dispose() : channelGroup = Nothing
            If tcClient.IsConnected Then
                dataStreamRead.Dispose() : dataStreamRead = Nothing
                tcClient.Disconnect() : tcClient.Dispose() : tcClient = Nothing
            End If
            'Jump to the CreateNewFile Sub to create the next TDMS file
            CreateNewFile()
        End If
    End While

    If logFile.IsOpen = True Then
        logFile.Close()
    End If
    If tcClient.IsConnected Then
        dataStreamRead.Dispose() : dataStreamRead = Nothing
        tcClient.Disconnect() : tcClient.Dispose() : tcClient = Nothing
    End If
    writingData = False
End Sub

Private Sub CreateNewFile()
    'Create the new folder where the data file/s will reside
    Dim newFilename As String = dataFileName & "_" & fileNum
    Dim customFilePropertyNames() As String = {"Date"}
    Dim customFilePropertyVals() As String = {""}
    Dim newAuthor As String = logFile.Author
    Dim newDescription As String = logFile.Description
    Dim newTitle As String = logFile.Title
    Dim newPath1 As String = "C:\Users\dmckin01\Desktop\Data\" & folderName
    Dim newPath2 As String = "D:\Data\" & folderName
    fileNum += 1

    'Create the TDMS file and save it to the user specified directory
    customFilePropertyVals(0) = Date.Today.ToShortDateString()
    logFile.Close() 'Close the old logfile after we've gotten values/properties from it
    logFile.Dispose() : logFile = Nothing

    Try
        If Connectlocal = True Then
            logFile = New TdmsFile(newPath1 & "\" & newFilename & ".tdms", New TdmsFileOptions())
        Else : logFile = New TdmsFile(newPath2 & "\" & newFilename & ".tdms", New TdmsFileOptions())
        End If
    Catch ex As Exception
        MessageBox.Show("Directory not created.  Make sure the TDMS file and/or directory that you are referencing are not already currently opened.", "Directory Creation Failed", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Exit Sub
    End Try
    logFile.Author = newAuthor
    logFile.Description = newDescription
    logFile.Title = newTitle
    logFile.AddProperty(customFilePropertyNames(0), TdmsPropertyDataType.String, customFilePropertyVals(0))
    logFile.AutoSave = True
    dataAcquiring()
End Sub

【问题讨论】:

  • 两件事。 1)您绝对应该将日志添加到您的应用程序中。 2)你有递归吗?如果是,请将其删除。
  • 代码添加到原始帖子。两个 Subs 都包含在内。这在应用程序的单独线程中运行。
  • 无限递归似乎很明显,因为您正在调用另一个方法。去掉最后对dataAcquiring()的调用,CreateNewFile应该是一个返回文件名的函数。
  • @Slai - 好的,我一直想知道这是否是我这样做的问题。由于它可以运行几个小时而没有问题,我认为它可以正常运行。我/我对递归一无所知。它真的是“无限”吗,因为有一种简单的退出方式,它不是无限循环。尽管如此,我还是会按照建议重新处理那部分代码。谢谢。
  • True .. 它并不是真正的无限,因为它最终会耗尽堆栈空间并抛出 StackOverflow 异常。每个方法都将信息存储在堆栈上,例如返回地址和局部变量,直到将执行返回给调用者。您还可以查看一些现有的记录信息的方法msdn.microsoft.com/en-us/library/12xxftw2.aspx

标签: vb.net recursion memory stack-overflow callstack


【解决方案1】:

堆栈错误总是由代码中的循环自行调用引起的。通常由设置其他属性的属性设置处理程序引起,这些属性又尝试设置初始属性。有时它们可​​能很难确定。

在你的情况下,你已经调用了日志功能

    dataAcquiring()
End Sub

在文件创建例程结束时...这是一个严重的错误。

每次您启动一个新文件时,您都会启动一个新的日志循环实例,而旧的仍会保留在堆栈中...空间用完只是时间问题

在这种情况下......创建例程应该退出......

但是,如果是我,我会让该代码成为返回 true 或 false 的函数。如果由于某种原因无法创建文件,让它返回 false 并在主循环中优雅地处理它。

【讨论】:

  • 感谢特雷弗的指点和 cmets。日志文件只有在满时才会关闭,因此在实际写入数据期间它会保持打开状态。一个“完整的”数据文件将是 34 个机器周期。大约每 2.125 分钟就会填充一个数据文件,并且必须创建一个新文件。那是我必须关闭日志文件的时候。程序退出 while 循环的唯一情况是 UI 上的 Record 按钮被取消单击,或者它必须创建一个新的日志文件,因为当前日志文件已满。我必须重新实例化所有通道,因为 NI 缺少 API 中的方法来优雅地完成它。
  • 哦..我在粗略研究您的代码时错过了循环.. TMI .. 大声笑那是您的问题,每次创建新文件时,您都会启动另一个记录器广告-无限。
  • 我想我没有关注。在 dataAcquiring() 结束时,在它跳转到创建一个新文件之前,我正在杀死所有内容......然后重新创建所有内容。
  • 您在永久循环中调用 CreateNEwFIle....但是 CreateNEwFIle 调用 dataAquiring..开始一个全新的循环..
  • 事实上 createNewFile 不会返回,直到您停用记录按钮。此时它应该全部折叠起来......尽管还有待观察。
猜你喜欢
  • 2012-06-26
  • 1970-01-01
  • 2015-10-24
  • 2015-12-29
  • 2017-12-27
  • 2020-12-06
  • 2016-04-06
  • 1970-01-01
  • 2012-02-10
相关资源
最近更新 更多