© 2004 Microsoft Corporation. All rights reserved.
Figure 1 Tracing Facilities in .NET
TraceListener
Properties
Trace
Properties
Indent
Indent
IndentSize
IndentSize
Name
Listeners
Methods
Methods
Close
WriteLineIf
Fail
Assert
Flush
Close
Write
Fail
WriteLine
Flush
Figure 3 ILDASM Output for Code Compiled with /d:TRACE=True

Figure 3 ILDASM Output for Code Compiled with /d:TRACE=True
Figure 4 ILDASM Output for Code Compiled with /d:TRACE=False

Figure 4 ILDASM Output for Code Compiled with /d:TRACE=False
Figure 5 Writing a Message to an Event Log
Public Class FileMonitorService
Private ELog As EventLog = New EventLog("", ".", "BatchImporter")
Private Shared ELogListener As EventLogTraceListener = _ 
  New EventLogTraceListener(ELog)
'The following name must match the registry value         'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\COMPlus\Switches\TraceExample
'Set DWORD value of the following key to 
'0 (off), 1 (error), 2 (warning), 3 (info), or 4 (verbose)
Private Shared BITraceLevel As New TraceSwitch( _ 
"TraceExample", "Trace Example Trace Level")
'other declarations omitted
Public Sub New()
   Trace.Listeners.Add(ELogListener)
End Sub
Public Sub StartImport()
   'other code omitted
   Trace.WriteLineIf(BITraceLevel.TraceVerbose, 
                     "Starting import process")
    'other code omitted
     Trace.WriteLineIf(BITraceLevel.TraceError, _ 
      "An error was encountered")
End Sub
End Class
Figure 6 Creating, Interrupting, and Aborting a Worker
Imports System.IO
Imports System.Threading
Module Module1
Sub Main()
     Dim fp As New FileProcessor()
     fp.FilePath = "D:\InputFiles\largeFile.txt"
     Dim t As New Thread(New ThreadStart(AddressOf fp.ProcessFile))
     t.Start()
     Console.WriteLine( _ 
         "From main thread: Worker thread state is {0}" , _ 
    t.ThreadState)
While (True)
         'wait until the worker thread enters the WaitSleepJoin state
         Thread.Sleep(50)
         If t.ThreadState <> ThreadState.Unstarted Then
             Console.WriteLine( _ 
            Console.WriteLine("Enter i to interrupt, a to abort.")
            Select Case Console.ReadLine()
                Case "i"
                    t.Interrupt()
                    t.Join()
                    console.WriteLine( _ 
                    "Worker thread state is now {0} ", t.ThreadState)
                    Exit While
                Case "a"
                  Console.WriteLine( _ 
                  "Worker thread state is now {0} ", t.ThreadState)
                    t.Abort()
                    t.Join()
                   Console.WriteLine( _ 
                   "Worker thread state is now {0} ", t.ThreadState)
                    Exit While
            End Select
         End If
End While
      Console.WriteLine( _ 
      "Thread has finished executing. Press Enter to shutdown.")
     Console.ReadLine()
 End Sub
'_____________________________________________________________________
 Class FileProcessor
     Public FilePath As String
     Public Sub ProcessFile()
         Try
           Console.WriteLine( _ 
       "From worker thread: Thread is now running. Entering sleep state")            
                Thread.Sleep(60000)         
          Catch e As System.Exception
             Console.WriteLine( _ 
                 "Catch block executing. Exception is : " & _ 
                 e.ToString)
         Finally
             Console.WriteLine("Finally block executing")
         End Try
     End Sub
 End Class
End Module
Figure 7 System.ServiceProcess.ServiceBase Class
Properties
Description
AutoLog
If true, ServiceBase will automatically report service start, stop pause, and continue to the event log
CanPauseAndContinue
The service is able to accept and process pause and continue commands
CanShutdown
The service needs to know when the system is shutting down
CanStop
The service is able to accept stop commands. You'll set this to True for most services you write
EventLog
A read-only property that gives you access to an EventLog object that you can use to write to the event log
ServiceName
You set this property to indicate the name of the service. ServiceBase uses this name as the source for messages it writes to the event log
Methods
Description
Dispose
Call this method to dispose of the object immediately rather than waiting for the garbage collector to dispose of it
OnContinue
Override this method in your class to receive notifications of the continue command. You must also set CanPauseAndContinue to True
OnCustomCommand
Override this method to process custom commands sent to your service. A custom command is an integer between 128 and 256 that means something to your service. The integer representing the command that was issued is passed in as a parameter to this method
OnPause
Override this method in your class to receive notifications of the pause command. You must also set CanPauseAndContinue to True
OnShutdown
Override this method in your class to receive notifications when the system is shutting down. You must also set CanShutdown to True
OnStart
Override this method in your class to receive notifications of the Start command
OnStop
Override this method in your class to receive notifications of the stop command. You must also set CanStop to True
Run
The only shared method on this class. Call this method from your program's Main procedure, passing it an instance of the class that inherits from ServiceBase. This tells ServiceBase to load the service in memory so it can be started by the Service Control Manager
Figure 8 An Example Service
Public Class FileMonitorService : Inherits ServiceBase
    'other code ommitted
    Private Const myName As String = "BatchImporter"
    Private t As Threading.Thread
    Public Sub New()
        MyBase.New()
        Me.CanPauseAndContinue = True
     Me.ServiceName = myName
End Sub
Protected Overrides Sub OnStart(ByVal args() As String)
     Trace.WriteLineIf(BITraceLevel.TraceVerbose, _ 
         "Batch Importer service started")
     Me.StartMonitorThread()
End Sub
Protected Overrides Sub OnStop()
    Trace.WriteLineIf(BITraceLevel.TraceVerbose, _ 
       "Batch Importer service stopped")
    StopThread()
    MyBase.Dispose()
End Sub
Protected Overrides Sub OnPause()
    Trace.WriteLineIf(BITraceLevel.TraceVerbose,_ 
        "Attempting to interrupt monitor thread")
    StopThread()
End Sub
Protected Overrides Sub OnContinue()
    Trace.WriteLineIf(BITraceLevel.TraceVerbose, _ 
        "Attempting to resume monitor thread")
    StartMonitorThread()
        Trace.WriteLineIf(BITraceLevel.TraceVerbose, _ 
            "Monitor thread started successfully")
End Sub
Private Sub StopThread()
    t.Interrupt()
    t.Stop()
    t.Join()
    t = Nothing
    Trace.WriteLineIf(BITraceLevel.TraceVerbose, _
        "Monitor thread stopped successfully")
End Sub    
Public Sub StartMonitorThread()
     'here we do the real work
     Dim mon As New MonitorFiles()
     t = New Threading.Thread(New _ 
         Threading.ThreadStart(AddressOf mon.StartMonitor))
     Trace.WriteLineIf(BITraceLevel.TraceVerbose, _ 
         "Starting monitor thread")
     t.Start()
    End Sub
End Class
Figure 9 Installer Component
Imports System
Imports System.Collections
Imports System.Configuration.Install
Imports System.ServiceProcess
Imports System.ComponentModel

Public Class <RunInstaller(True)> ProjectInstaller : Inherits Installer

    Private serviceInstaller As ServiceInstaller
    Private processInstaller As ServiceProcessInstaller

    Public Sub New()
        MyBase.New()

        processInstaller = New ServiceProcessInstaller()
        serviceInstaller = New ServiceInstaller()

        ' Service will run under system account
        processInstaller.RunUnderSystemAccount = True

        ' Service will have Start Type of Manual
        serviceInstaller.StartType = ServiceStart.Manual
        serviceInstaller.ServiceName = "Batch Importer Service"
        Installers.Add(serviceInstaller)
        Installers.Add(processInstaller)
    End Sub
End Class
Figure 10 Using System.IO.FileSystemWatcher
Public Sub StartMonitor()
    'create the file system watcher
    Dim fw As New FileSystemWatcher()
    Dim result As WaitForChangedResult
    fw.Path = dirpath
    'watch only for changes in files
    fw.Target = IO.WatcherTarget.File
    'do not recursively watch subdirectories
    fw.IncludeSubdirectories = False
    'set the filter
    fw.Filter = "*.xml"
    'set the event handlers via delegates
    AddHandler fw.Created, New FileSystemEventHandler( _ 
    AddressOf Me.OnFileCreated)

    'tell it to start watching
    fw.Enabled = True
    Try
        Do
            result = fw.WaitForChanged( _ 
            WatcherChangeTypes.Created)
        Loop
    Catch e As Exception
        Trace.WriteLineIf(BITraceLevel.TraceError, _ 
        "An exception occurred while waiting for file: " & _ 
        e.ToString())
    End Try
End Sub

'Event handler for Created event
Public Sub OnFileCreated(ByVal source As Object, _ 
    ByVal e As FileSystemEventArgs)
'other code omitted
End Sub
Figure 11 Checking File Status
'•••
'Other code omitted
Dim i As Integer
For i = 0 To 2
    If GetExclusiveAccess(fileInfo.FilePath) Then
        Exit For
    ElseIf i = 2 Then
        Trace.WriteLineIf(BITraceLevel.TraceError, _ 
            "Could not obtain exclusive access to file " & _ 
            fileInfo.FilePath)
        Exit Sub
    End If
    Trace.WriteLineIf(BITraceLevel.TraceInfo, _
        "Retrying file " & CStr(fileInfo.filenumber))
    Thread.Sleep(5000)
Next
'the file is closed so we can process it 
'Other code omitted
'•••

Private Function GetExclusiveAccess( _ 
ByVal FilePath As String) As Boolean
    Try
        Dim theFile As New File(FilePath)
        Dim strm As Stream = theFile.Open( _ 
            IO.FileMode.Append, _ 
            IO.FileAccess.Write, _ 
            IO.FileShare.None)
        'if we succeed, we can let it go
        strm.Close()
        GetExclusiveAccess = True
    Catch e As Exception
        Trace.WriteLineIf(BITraceLevel.TraceInfo, _ 
            "Error opening input file " & _ 
            FilePath & " exception info: " & e.ToString)
        GetExclusiveAccess = False
    End Try
End Function
Figure 12 Service Startup Sequence

Figure 12 Service Startup Sequence
Figure 13 Service Startup Code
Private Const myName As String = "BatchImporter"
Public Sub Main()
    Dim BIService As New FileMonitorService()
    BIService.AutoLog = True
  #If DEBUG Then
        BIService.StartMonitorThread()
  #Else
      ServiceBase.Run(BIService)
  #End If
End Sub
'_____________________________________________________________________
Public Class FileMonitorService : Inherits ServiceBase
    'these two are shared because they are accessed
    'from the Trace class methods which are called
    'from other objects
    Private Shared ELog As EventLog = New EventLog( _ 
        "", ".", "BatchImporter")
    Private Shared ELogListener As EventLogTraceListener = _ 
        New EventLogTraceListener(ELog)

    Private Shared BITraceLevel As New TraceSwitch(myName, _ 
        "Batch Importer Trace Level")
    Private t As Threading.Thread
    Private strm As System.IO.Stream
    Public Sub New()
        MyBase.New()
        Me.CanPauseAndContinue = True
        Me.ServiceName = myName
        Trace.Listeners.Add(ELogListener)
    End Sub
    Public Sub StartMonitorThread()
        Dim mon As New MonitorFiles()
        ELogListener.EventLog = ELog
        t = New Threading.Thread(New Threading.ThreadStart( _ 
            AddressOf mon.StartMonitor))
        t.Start()
    End Sub
    Protected Overrides Sub OnStart(ByVal args() As String)
        StartMonitorThread()
    End Sub
    Protected Overrides Sub OnStop()
        StopThread()
        MyBase.Dispose()
    End Sub
    Protected Overrides Sub OnPause()
        StopThread()
    End Sub
    Protected Overrides Sub OnContinue()
        StartMonitorThread()
    End Sub
    Private Sub StopThread()
        t.Interrupt()
        t.Stop()
        t.Join()
        t = Nothing
    End Sub
End Class
'_____________________________________________________________________
Private Class MonitorFiles
    Private Shared BITraceLevel As New TraceSwitch(myName, _ 
        "Batch Importer Trace Level")
    Const dirpath = "C:\temp\"

    'other code omitted . . .

    Public Sub StartMonitor()
        Dim fw As New FileSystemWatcher()
        Dim result As WaitForChangedResult
        fw.Path = dirpath
        fw.Target = IO.WatcherTarget.File
        fw.IncludeSubdirectories = False
        fw.Filter = "*.xml"            
        AddHandler fw.Created, New FileSystemEventHandler( _ 
            AddressOf Me.OnFileCreated)
        fw.Enabled = True
        Try
            Do
              result = fw.WaitForChanged( _ 
                       WatcherChangeTypes.Created)
            Loop
        Catch e As Exception
            
        Finally
            'stop monitoring the directory
            fw.Enabled = False
        End Try
    End Sub
End Class
Figure 15 Handling File-created Callbacks
Private Class MonitorFiles
 Private Shared BITraceLevel As New TraceSwitch(myName, _ 
     "Batch Importer Trace Level")
 Const dirpath = "C:\temp\"
 Public Sub OnFileCreated(ByVal source As Object, _ 
     ByVal e As FileSystemEventArgs)
      Try
          Dim fileinfo As New FileInformation(e.FullPath)
          'create new instance of importer 
          Dim worker As New Importer()
          ThreadPool.QueueUserWorkItem(New WaitCallback( _ 
              AddressOf worker.ProcessFile), fileinfo)
      Catch ex As Exception
          Trace.WriteLineIf(BITraceLevel.TraceError, _ 
              "An exception occurred while queuing file : " & _ 
              ex.ToString())
      End Try
 End Sub
 'other code omitted
End Class
'_____________________________________________________    
Public Class FileInformation
    Public FilePath As String
    Private Shared num As Integer
    Private myFileNumber As Integer
    Public Sub New(ByVal file As String)
        MyBase.New()
        FilePath = file
        Interlocked.Increment(num)
        myFileNumber = num
    End Sub
    'read/only property
    Public ReadOnly Property FileNumber() As Integer
        Get
            FileNumber = myFileNumber
        End Get
    End Property
End Class
Figure 16 Processing the New File

Figure 16 Processing the New File
Figure 17 Processing Newly Created Files
Public Sub ProcessFile(ByVal fileInfo As Object)
    'try to get exclusive access to the file first
    Dim i As Integer
    For i = 0 To 2
        If GetExclusiveAccess(fileInfo.FilePath) Then
            Exit For
        ElseIf i = 2 Then
            Exit Sub
        End If
        Thread.Sleep(5000)
    Next
    'import the data from the file
    ImportData(fileInfo)            
    Dim f As New File(fileInfo.FilePath)
    f.Delete()
End Sub

'GetExclusiveAccess omitted

Public Sub ImportData(ByVal fileInfo As FileInformation)
    Dim doc As New XmlDocument()
    Dim orders As XmlNodeList
    Dim order As XmlElement
    Dim employee As XmlElement
    Dim product As XmlElement
    Try
        'establish database connection
        cmd.ActiveConnection = New SQLConnection( _ 
            "user id=sa;password=;database=orders;server=pluto")
    cmd.ActiveConnection.Open()
  Catch e As Exception
     Exit Sub
  End Try
  Try
    'open the document
    doc.Load(fileInfo.FilePath)
    Dim navigator As New DocumentNavigator(doc)
    navigator.PushPosition()
    'find the first order
    navigator.Select("/orders/order")
    navigator.MoveToFirstSelected()
    Dim i As Integer = 0
    'iterate over all orders
    While (navigator.MoveToNext())
        order = navigator.GetNode()
        currentOrder.id = CLng(order.GetAttribute("id"))
  'other code omitted        
        currentOrder.Quant = product.FirstChild.InnerText
        InsertOrder(currentOrder)
        i = i + 1
    End While
    Catch e As Exception

    End Try
 End Sub
 Private Sub InsertOrder(ByVal currentOrder As Order)
     'here we'll insert the order into a database
     Dim sql As String
     Dim sb As New StringBuilder(currentOrder.Company)
     currentOrder.Company = sb.Replace("'", "''").ToString
     sql = "INSERT INTO SALES ..." 
     'other code omitted . . . 
     Try
         cmd.CommandText = sql
         cmd.ExecuteNonQuery()
     Catch e As Exception
         
     End Try
 End Sub
End Class

MSDN Magazine Blog

MSDN Magazine Right Rail

14 Top Features of Visual Basic 14: The Q&A
Leading off the feature in the January issue of MSDN Magazine is Lucian Wischik’s fantastic look at Visual Basic .NET 14. As Wischik writes, the newes... More...
Wednesday, Jan 7
Big Start to the New Year at MSDN Magazine
Folks, things are hopping over here at MSDN Magazine. We are kicking off the new year with a pair of issues: Our regularly scheduled January issue and... More...
Friday, Jan 2

More MSDN Magazine Blog entries >


Receive the MSDN Flash e-mail newsletter every other week, with news and information personalized to your interests and areas of focus.