我在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应用程序中从未尝试过)。 关于如何补救的任何想法?
答案 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
就是这样 - 只需将变量传递给调用者