winforms中定义的全局变量未按预期工作

时间:2016-10-07 23:19:16

标签: vb.net multithreading winforms global-variables

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

程序启动多个测试工作进程(检查来自不同来源的多个数据库连接,来自多个来源的远程Web服务,网络检查等),每个进程条都有进度条。如果在任何这些测试期间发生错误,则需要记录错误。 问题是,程序无法将事件记录到Windows事件系统,因为它不能在管理员帐户下运行(因此,由于MS决定阻止在普通用户帐户下进行日志记录,因此无法进行日志记录w / 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的计数在启动事件之后是(1),然后它的计数是来自任何子进程的(0),最后,计数是(1)当列表被转储到错误日志文件时(仅添加了第一个事件)。它显然就像有多个版本的AppEvents变量一样。

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

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

1 个答案:

答案 0 :(得分:1)

如上所述,我不得不将事件传递回调用workerprocess,因此,在我输入的主要表单中:

   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

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