【发布时间】: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