2016-10-07 80 views
0

我在VB.Net一個項目,需要一個全局變量(事件日誌對象的列表)全程不工作。這似乎很簡單,只需在應用程序的StartUp事件中定義變量並初始化,然後在整個應用程序中訪問它。不幸的是,這似乎只當沒有子進程工作(他們有全局變量沒有接入) - 所以我在我的頭上,如何從孩子的工作進程訪問全局變量(如果這甚至有可能的方式)。全局變量如預期

該程序將啓動幾個測試工作進程(即檢查從多個來源,網絡檢測等來自不同源的多個數據庫連接,遠程Web服務)W /進度條每個。如果在這些測試中發生錯誤,則需要記錄錯誤。 問題是,程序無法將事件記錄到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 

接下來,在我的日誌類,我有一些方法用於記錄,刷新/寫入事件(s)

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塊簡單地捕捉異常,並將其寫入到存儲器(I剛剛結束它在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 

只有第一個事件顯示出來,而不是(它們被添加,沒有空裁判異常或任何例外 - 如幻影)的其他事件。任何添加到主線程上的全局變量的事件都會保留(最終會被記錄)。所以這顯然是一個多線程問題(從未在Windows應用程序中嘗試過)。 關於如何補救的任何想法?

+0

那是一個很多代碼。除了'MyApplication_Startup',我無法看到你在列表中添加什麼東西。請參閱[MCVE] – Plutonix

+0

WriteEventToMemory方法是將AppEvent添加到AppEvents列表的唯一位置。 我真的不能做一個mutlthreaded示例比任何更小(除非我只是刪除) – MC9000

+0

我剛剛遇到了解決方案 - 全局變量不是一個線程安全的即使它的工作。 http://stackoverflow.com/questions/7563825/global-variable-in-asp-net#7564075 – MC9000

回答

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#出)。 當工人過程完成後,我添加以下內容:

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 

這就是它 - 只是傳遞變量返回給調用者