2016-10-07 16 views
0

私はVB.Netに、グローバル変数(イベントログオブジェクトのリスト)を必要とするプロジェクトを持っています。これは、アプリケーションのStartUpイベントで変数を定義して初期化してから、アプリケーション全体にアクセスするだけで簡単です。残念ながら、これは子プロセスがない(グローバル変数へのアクセス権がない)場合にのみ有効です - 子プロセスからグローバル変数にアクセスする方法として頭を悩ませています)。winformsで定義されたグローバル変数が正常に動作しない

プログラムは複数のテストワーカープロセス(それぞれ異なるソースからの複数のDB接続、複数のソースからのリモートWebサービス、ネットワークチェックなど)をそれぞれの進行状況バーでチェックします。これらのテスト中にエラーが発生した場合は、エラーを記録する必要があります。 問題は、プログラムがWindowsイベントシステムにイベントを記録することができないということです。これは、管理者アカウントでは実行されないためです(Vistaの通常のユーザーアカウントでのロギングを防止するというMSの決定のおかげで、 、8,10)、プログラムは非同期であるためテキストファイルにもログできず、ファイルアクセスの競合の問題(すぐにテキストファイルにイベントを記録することはできません) /メモリ内のエラー(グローバル変数)、THENはすべての子プロセスが完了した後にログファイルにダンプします。意味をなさない?私のロギングクラスで私は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です(AppEventsをAppEventsリストに追加するだけです)。

のように(ローカルデータベースへの)サンプルテストルーチン/ワーカープロセスが見えます:try-catchブロックは、単に例外をキャッチし、メモリに書き込む

#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 

(私はちょうどWriteEventToMemory方法でそれを包んAppEventsのカウントがStartupイベントの後に(1)あったことが気付くまで、すべてがピーチになっているように見えましたが、それはAppEventsのリストに追加しています:My.Application.AppEvents.Add(evt)

カウントは(0)子プロセスのいずれかから、最後にカウントは(1)リストがエラーログファイルにダンプされたとき(追加された最初のイベントはそこにあった)。 AppEvents変数の複数のバージョンがあるように、明らかに動作しています。

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

のみ最初のイベントではなく、他のイベントを示している( - ファントムのようにそれらが追加され、ヌル参照例外またはいずれかの例外はありません)。 MAINスレッドのグローバル変数に追加されたイベントは、そのまま残ります(最終的にはログに記録されます)。だからこれは明らかにマルチスレッドの問題です(Windowsアプリケーションでこれまで試みたことはありません)。 救済方法に関するアイデアはありますか?

+0

が厥コードの多く。 'MyApplication_Startup'以外では、どこにリストに何を追加しているのかわかりません。 [MCVE] – Plutonix

+0

を参照してください。WriteEventToMemoryメソッドは、AppEventをAppEventsリストに追加する唯一の場所です。 私は本当にそこにあるものよりも小さなスレッド化された例を作ることはできません。(私がそれを削除しない限り) – MC9000

+0

私はちょうど解決策に突き当たりました - グローバル変数は動作してもスレッドセーフではありません。 http://stackoverflow.com/questions/7563825/global-variable-in-asp-net#7564075 – MC9000

答えて

1

上述したように、私は、呼び出し元のworkerprocessにイベントを渡す必要があったので、私は入れメインフォームで:(各工程について)DoWorkで

Private AppEvent_TestLocalDB As New AppEvent 

、私はそれを変更しました:

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 subが今になります。エラーログだけで、存在しない

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 

- ちょうど呼び出し元に戻って変数を渡す

関連する問題