【问题标题】:global variable defined in winforms not working as expectedwinforms 中定义的全局变量未按预期工作
【发布时间】:2016-10-07 23:19:15
【问题描述】:

我在 VB.Net 中有一个项目,它需要一个全局变量(事件日志对象列表)。这看起来很简单,只需定义变量并在应用程序的启动事件中初始化,然后在整个应用程序中访问它。不幸的是,这似乎只在没有子进程时才有效(他们无法访问全局变量) - 所以我对于如何从子工作进程访问全局变量(如果这是可能的话) )。

程序启动多个测试工作进程(检查来自不同来源的多个数据库连接、来自多个来源的远程 Web 服务、网络检查等),每个进程都有进度条。如果在任何这些测试期间发生错误,则需要记录该错误。 问题是,该程序无法将事件记录到 Windows 事件系统,因为它不会在管理员帐户下运行(因此,由于 MS 决定阻止使用 Vista 的普通用户帐户登录,因此无法进行记录,7, 8,10),程序也无法登录到文本文件,因为它是异步的并且文件访问争用问题(立即将事件记录到文本文件将不起作用),所以我希望记录任何事件/内存中的错误(全局变量),然后在所有子进程完成后将其转储到日志文件中。有意义吗?

我创建了一个名为 AppEvent 的类

Public Class AppEvent

    Sub New()
        EventTime_ = Date.Now
        Level_ = EventLevel.Information
        Description_ = String.Empty
        Source_ = String.Empty
    End Sub

    Private EventTime_ As Date
    Public Property EventTime() As Date
        Get
            Return EventTime_
        End Get
        Set(ByVal value As Date)
            EventTime_ = value
        End Set
    End Property

    Private Level_ As EventLevel
    Public Property Level() As EventLevel
        Get
            Return Level_
        End Get
        Set(ByVal value As EventLevel)
            Level_ = value
        End Set
    End Property

    Private Description_ As String
    Public Property Description() As String
        Get
            Return Description_
        End Get
        Set(ByVal value As String)
            Description_ = value
        End Set
    End Property

    Private Source_ As String
    Public Property Source() As String
        Get
            Return Source_
        End Get
        Set(ByVal value As String)
            Source_ = value
        End Set
    End Property

End Class
Public Enum EventLevel
    [Information]
    [Warning]
    [Error]
    [Critical]
    [Fatal]
End Enum

并为此创建一个公共变量(并将初始事件添加到 AppEvents 列表中)

Namespace My
    Partial Friend Class MyApplication
        'global variable here (using this for logging asynch call errors, then dumping this into a log file when all asynch calls are complete (due to file contention of log file)
        Public AppEvents As New List(Of AppEvent)

        Private Sub MyApplication_Startup(ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupEventArgs) Handles Me.Startup
            'create first event and add it to the global variable declared above
            AppEvents.Add(New AppEvent With {.EventTime = Now, .Description = "Program Started", .Level = EventLevel.Information, .Source = "QBI"})

        End Sub
    End Class
End Namespace

接下来,在我的日志记录类中,我有一些用于记录、刷新/写入事件的方法

Public Class AppLogging

    Public Shared Sub WriteEventToAppLog(evt As AppEvent)
        LogDataToFile(FormatLineItem(evt.EventTime, evt.Level, evt.Description, evt.Source))
    End Sub

    Public Shared Sub WriteEventsToAppLog(AppEvents As List(Of AppEvent))
        Dim sbEvents As New StringBuilder
        If AppEvents.Count > 0 Then
            For Each evt In AppEvents
                sbEvents.AppendLine(FormatLineItem(evt.EventTime, evt.Level, evt.Description, evt.Source))
            Next
            LogDataToFile(sbEvents.ToString.TrimEnd(Environment.NewLine))
        End If
    End Sub

    Private Shared Function FormatLineItem(eventTime As Date, eventLevel As EventLevel, eventDescr As String, eventSource As String) As String
        Return String.Format("Logged On: {0} | Level: {1} | Details: {2} | Source: {3}", eventTime, System.Enum.GetName(GetType(EventLevel), eventLevel).Replace("[", "").Replace("]", ""), eventDescr, eventSource)
    End Function

    Private Shared Sub LogDataToFile(eventLogText As String, Optional ByVal LogFileName As String = "Error.log", Optional ByVal HeaderLine As String = "****** Application Log ******")
        'log file operations
        Dim LogPath As String = System.AppDomain.CurrentDomain.BaseDirectory()
        If Not LogPath.EndsWith("\") Then LogPath &= "\"
        LogPath &= LogFileName

        Dim fm As FileMode
        Try
            If System.IO.File.Exists(LogPath) Then
                fm = FileMode.Append
            Else
                fm = FileMode.Create
                eventLogText = HeaderLine & Environment.NewLine & eventLogText
            End If
            Using fs As New FileStream(LogPath, fm, FileAccess.Write)
                Using sw As New StreamWriter(fs)
                    sw.WriteLine(eventLogText)
                End Using
            End Using
            My.Application.AppEvents.Clear() 'clears the global var
        Catch ex As Exception
            'handle this
        End Try
    End Sub


    Public Shared Sub WriteEventToMemory(eventLevel As EventLevel, eventDescription As String, Optional eventSource As String = "")
        Dim evt As New AppEvent
        evt.Description = eventDescription
        evt.Level = eventLevel
        evt.EventTime = Now
        evt.Source = eventSource
        Try
            My.Application.AppEvents.Add(evt)
        Catch ex As Exception
            Throw
        End Try
    End Sub

    Public Shared Sub FlushEventsToLogFile()
        WriteEventsToAppLog(My.Application.AppEvents)
    End Sub

End Class

这里有几个方法,但在每个异常处理程序中调用的方法是 WriteEventToMemory(它只是将一个 AppEvent 添加到 AppEvents 列表中)。

一个示例测试例程/工作进程(本地数据库)如下所示:

#Region "local db test"
    Private Sub TestLocalDBWorkerProcess_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles TestLocalDBWorkerProcess.DoWork
        Me.TestLocalDBStatusMessage = TestLocalDB()
    End Sub

    Private Sub TestLocalDBWorkerProcess_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles TestLocalDBWorkerProcess.RunWorkerCompleted
        Me.prgLocalDatabase.Style = ProgressBarStyle.Blocks
        Me.prgLocalDatabase.Value = 100

        If Me.ForcedCancelTestLocalDB Then
            Me.lbStatus.Items.Add("Local DB Test Cancelled.")
        Else
            If TestLocalDBStatusMessage.Length > 0 Then
                Me.lblLocalDatabaseStatus.Text = "Fail"
                Me.lbStatus.Items.Add(TestLocalDBStatusMessage)
                SendMessage(Me.prgLocalDatabase.Handle, 1040, 2, 0) 'changes color to red
            Else
                Me.lblLocalDatabaseStatus.Text = "OK"
                Me.lbStatus.Items.Add("Connection to local database is good.")
                Me.prgLocalDatabase.ForeColor = Color.Green
            End If
        End If
        Me.ForcedCancelTestLocalDB = False
        Me.TestLocalDBProcessing = False
        ProcessesFinished()
    End Sub

    Private Sub StartTestLocalDB()
        Me.prgLocalDatabase.Value = 0
        Me.prgLocalDatabase.ForeColor = Color.FromKnownColor(KnownColor.Highlight)
        Me.prgLocalDatabase.Style = ProgressBarStyle.Marquee
        Me.TestLocalDBProcessing = True
        Me.TestLocalDBStatusMessage = String.Empty
        Me.lblLocalDatabaseStatus.Text = String.Empty
        TestLocalDBWorkerProcess = New System.ComponentModel.BackgroundWorker
        With TestLocalDBWorkerProcess
            .WorkerReportsProgress = True
            .WorkerSupportsCancellation = True
            .RunWorkerAsync()
        End With
    End Sub

    Private Function TestLocalDB() As String
        Dim Statusmessage As String = String.Empty
        Try
            If Me.TestLocalDBWorkerProcess.CancellationPending Then
                Exit Try
            End If
            If Not QBData.DB.TestConnection(My.Settings.DBConnStr3) Then
                Throw New Exception("Unable to connect to local database!")
            End If
        Catch ex As Exception
            Statusmessage = ex.Message
            AppLogging.WriteEventToMemory(EventLevel.Fatal, ex.Message, "TestLocalDB")
        End Try
        Return Statusmessage
    End Function
#End Region

try-catch 块只是捕获异常并将其写入内存(我只是将其包装在 WriteEventToMemory 方法中,但它只是将其添加到 AppEvents 列表中:My.Application.AppEvents.Add(evt)

一切似乎都很顺利,直到我注意到 AppEvents 的计数在 Startup 事件之后是 (1),然后它的计数是 (0) 从任何子进程开始,最后,计数是 (1) 当该列表被转储到错误日志文件中(只有第一个添加的事件在那里)。显然,它的行为就像 AppEvents 变量有多个版本一样。

****** Application Log ******
Logged On: 10/7/2016 6:01:45 PM | Level: Information | Details: Program Started | Source: QBI

只有第一个事件显示,其他事件不显示(它们被添加,没有空引用异常或任何异常 - 像幻像)。在主线程上添加到全局变量的任何事件都会保留(并最终被记录)。所以这显然是一个多线程问题(以前从未在 Windows 应用程序中尝试过)。 关于如何补救的任何想法?

【问题讨论】:

  • 代码太多了。除了MyApplication_Startup 我看不到你在哪里添加任何东西到列表中。见minimal reproducible example
  • WriteEventToMemory 方法是唯一将 AppEvent 添加到 AppEvents 列表的地方。我真的无法制作比现有示例更小的多线程示例(除非我只是删除它)
  • 我刚刚遇到了解决方案 - 全局变量不是线程安全的,即使它确实有效。 stackoverflow.com/questions/7563825/…
  • 有专门的收藏——与范围无关
  • 我知道这一点,但是,EventLog 方法仅在应用程序作为管理员帐户运行时才有效 - 正如我之前提到的那样,它毫无价值。第二个障碍是试图让多个线程访问和写入一个简单的文本文件,但该文件可能会被其他进程保持打开(并且通常是)。

标签: vb.net multithreading winforms global-variables


【解决方案1】:

如上所述,我必须将事件传递回调用工作进程,因此,在我输入的主窗体中:

   Private AppEvent_TestLocalDB As New AppEvent

在DoWork中(针对每个流程),我改成:

Private Sub TestLocalDBWorkerProcess_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles TestLocalDBWorkerProcess.DoWork
    Me.TestLocalDBStatusMessage = TestLocalDB(AppEvent_TestLocalDB)
End Sub

TestLocalDB 子现在看起来像:

Private Function TestLocalDB(ByRef aEvent As AppEvent) As String
    Dim Statusmessage As String = String.Empty
    Try
        If Me.TestLocalDBWorkerProcess.CancellationPending Then
            Exit Try
        End If
        If Not QBData.DB.TestConnection(My.Settings.DBConnStr3) Then
            Throw New Exception("Unable to connect to local database!")
        End If
    Catch ex As Exception
        Statusmessage = ex.Message
        With aEvent
            .Description = ex.Message
            .Level = EventLevel.Fatal
            .Source = "TestLocalDB"
        End With
    End Try
    Return Statusmessage
End Function

注意没有错误记录,只有事件变量(ByRef 把它传回来,相当于C# out)。 当工作进程完成时,我添加以下内容:

    With AppEvent_TestLocalDB
        AppLogging.WriteEventToMemory(.Level, .Description, .Source)
    End With

(其他工作进程的工作方式相同) 当所有进程都完成后,我将其刷新到日志文件中。

   AppLogging.FlushEventsToLogFile()

现在自定义事件/错误日志条目如下所示(使用新制作的文件):

****** Application Log ******
Logged On: 10/7/2016 10:14:36 PM | Level: Information | Details: Program Started | Source: QBI
Logged On: 10/7/2016 10:14:53 PM | Level: Fatal | Details: Unable to connect to local database! | Source: TestLocalDB

就是这样 - 只需将变量传回给调用者

【讨论】:

    猜你喜欢
    • 2020-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    • 1970-01-01
    • 2015-07-10
    • 2018-02-13
    • 2021-07-18
    相关资源
    最近更新 更多