Search code examples
c#.netvb.netwmiresponsiveness

How to increase the responsiveness of 'ManagementEventWatcher' logic?


I'm suscribed to Win32_ProcessStartTrace class events to notify when a process is ran on the PC:

Me.processStartWatcher = 
    New ManagementEventWatcher(New WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace"))

However, this seems not efficient because it takes 1 or 2 seconds to fire the event after I ran an executable file:

Private Sub ProcessStartWatcher_EventArrived(sender As Object, e As EventArrivedEventArgs) _
Handles processStartWatcher.EventArrived

    If (Me.ProcessStartedEvent IsNot Nothing) Then
        RaiseEvent ProcessStarted(Me, e)
    End If

End Sub

This means that if a process is ran and has exited quickly then I will not be notified.

Is there something I could do to increase responsiveness on a ManagementEventWatcher object?.

I tried to set the Timeout property but as the description of the member says, it seems does have nothing to do with this purpose.


Solution

  • You use the WITHIN clause in your WMI query to specify the polling interval. The article gives plenty of warning that using small intervals is not recommended. But forge ahead and try it anyway:

    Me.processStartWatcher = 
        New ManagementEventWatcher(
           New WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace WITHIN 0.1"))
    

    And you'll find out that it simply doesn't make any difference, the default is already as fast as it can be. This is architectural in Windows, the kernel has no 'hook' to generate a notification when a process is created. Important as it might be to, say, a virus scanner. They are forced to patch the OS to be able to jump in. The WMI provider that implements this query (C:\Windows\System32\wbem\KrnlProv.dll) does not do this.

    Another way to see this is to just implement it yourself with the Process class. For example:

    Public Class ProcessMonitor
        Public Event Started As Action(Of Integer)
        Public Event Stopped As Action(Of Integer)
    
        Public Sub New(interval As Integer)
            Me.interval = interval
            running = Scan()
            timer = New Threading.Timer(AddressOf callback, Nothing, interval, 0)
        End Sub
    
        Private Sub callback(state As Object)
            Dim active As HashSet(Of Integer) = Scan()
            Dim started As IEnumerable(Of Integer) = active.Except(running)
            Dim stopped As IEnumerable(Of Integer) = running.Except(active)
            running = active
            For Each pid As Integer In started
                RaiseEvent started(pid)
            Next
            For Each pid As Integer In stopped
                RaiseEvent stopped(pid)
            Next
            timer.Change(interval, 0)
        End Sub
    
        Private Function Scan() As HashSet(Of Integer)
            Dim ret As New HashSet(Of Integer)
            For Each proc As Process In Process.GetProcesses()
                ret.Add(proc.Id)
            Next
            Return ret
        End Function
    
        Private running As HashSet(Of Integer)
        Private timer As System.Threading.Timer
        Private interval As Integer
    End Class
    

    With a sample program that uses it:

    Module Module1
        Sub Main()
            Dim monitor As New ProcessMonitor(100)
            AddHandler monitor.Started, AddressOf Started
            AddHandler monitor.Stopped, AddressOf Stopped
            Console.ReadLine()
        End Sub
        Sub Started(pid As Integer)
            Console.WriteLine("Started: {0}", Process.GetProcessById(pid).ProcessName)
        End Sub
        Sub Stopped(pid As Integer)
            Console.WriteLine("Stopped: {0}", pid)
        End Sub
    End Module
    

    No difference.