Today I wanted a way to see which of my cameras were online at a certain point in time. Pinging them is a great way of doing that, it at least says they are there and the first two layers of the OSI model are working (if I remember correctly).

Doing a ping to another computer in VB.Net is easy.

vb.net
1
2
3
4
Dim _ping As New Ping
Dim _pingreply = _ping.Send(IpAddress, 2000)
If _pingreply.Status = IPStatus.Success Then
Like this.
Dim _ping As New Ping
Dim _pingreply = _ping.Send(IpAddress, 2000)
If _pingreply.Status = IPStatus.Success Then
Like this.

The first parameter of Ping.Send is the IpAddress as string and the second is the timeout in this case 2 seconds.

I want to ping 5 cameras at a time to worst case this will take 10 seconds. 10 seconds is a long time to wait for a user who’s attention span is 3 seconds. So we need a way to do this asynchronously.

So I create a grid with 2 columns and 5 rows. First column has the Ipaddresses and the second will show the result.

Here is how.

vb.net
1
2
3
4
5
6
7
8
9
10
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
  fGrid.Columns.Add("Ip", "Ip")
  fGrid.Columns.Add("Ping", "Ping")
  fGrid.Columns(0).Width = 100
  fGrid.Columns(1).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
  For i As Integer = 0 To 4
    Dim myRowIndex = fGrid.Rows.Add()
      fGrid.Rows(myRowIndex).Cells(0).Value = "10.216.110." & (11 + myRowIndex).ToString
  Next
End Sub
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
  fGrid.Columns.Add("Ip", "Ip")
  fGrid.Columns.Add("Ping", "Ping")
  fGrid.Columns(0).Width = 100
  fGrid.Columns(1).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
  For i As Integer = 0 To 4
    Dim myRowIndex = fGrid.Rows.Add()
      fGrid.Rows(myRowIndex).Cells(0).Value = "10.216.110." & (11 + myRowIndex).ToString
  Next
End Sub

Looks like this.

So now that we have that we can do the pings in another thread. So I made a button to spawn this thread and begin the process.

vb.net
1
2
3
4
Private fThread = New Thread(New ThreadStart(AddressOf ThreadProc))
fThread.IsBackground = True
fThread.Start()
        
Private fThread = New Thread(New ThreadStart(AddressOf ThreadProc))
fThread.IsBackground = True
fThread.Start()
		

and this will use the following things.

vb.net
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Private Sub ThreadProc()
        Parallel.For(0, 5, Sub(b)
                               CheckOnline(b)
                           End Sub)
    End Sub
 
Private Sub CheckOnline(ByVal rowindex As Integer)
        Dim _ping As New Ping
        Try
            Dim _pingreply = _ping.Send("10.216.110." & (11 + rowindex).ToString, 2000)
            If _pingreply.Status = IPStatus.Success Then
                SyncLock success
                    success.Add(rowindex)
                End SyncLock
            End If
        Catch ex As Exception
        End Try
    End Sub
Private Sub ThreadProc()
		Parallel.For(0, 5, Sub(b)
							   CheckOnline(b)
						   End Sub)
	End Sub

Private Sub CheckOnline(ByVal rowindex As Integer)
		Dim _ping As New Ping
		Try
			Dim _pingreply = _ping.Send("10.216.110." & (11 + rowindex).ToString, 2000)
			If _pingreply.Status = IPStatus.Success Then
				SyncLock success
					success.Add(rowindex)
				End SyncLock
			End If
		Catch ex As Exception
		End Try
	End Sub

Yep I used a parallel foreach that will spawn 5 more threads to do the pings in parallel for me.

But I also needed a way to tell my users I was doing something and a way to tell them it was all finished and the result.

I did that with a second thread that check the result in a list and sees if they are done, making sure I synchronize the lists add method because that is not threadsafe.

vb.net
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Net.NetworkInformation
 
Partial Public Class Form1
 
    Private fThread As Thread
    Private fThread2 As Thread
 
    Public Delegate Sub AddRowDelegate(ByVal column As Integer)
    Delegate Sub CheckOnlineDelegate(ByVal rowindex As Integer)
    Delegate Sub SetOnlineDelegate(ByVal rowindex As Integer)
 
    Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
        fGrid.Columns.Add("Ip", "Ip")
        fGrid.Columns.Add("Ping", "Ping")
        fGrid.Columns(0).Width = 100
        fGrid.Columns(1).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
        For i As Integer = 0 To 4
            Dim myRowIndex = fGrid.Rows.Add()
            fGrid.Rows(myRowIndex).Cells(0).Value = "10.216.110." & (11 + myRowIndex).ToString
        Next
    End Sub
 
    Private Sub ThreadProc()
        Parallel.For(0, 5, Sub(b)
                               Do While Not done.Contains(b)
                                   fGrid.Invoke(New AddRowDelegate(AddressOf AddRow), New Object() {b})
                                   Thread.Sleep(300)
                               Loop
                               fGrid.Invoke(New SetOnlineDelegate(AddressOf SetOnline), New Object() {b})
                           End Sub)
    End Sub
 
    Private Sub ThreadProc2()
        Parallel.For(0, 5, Sub(b)
                               CheckOnline(b)
                           End Sub)
    End Sub
 
    
    Private Sub AddRow(ByVal rowindex As Integer)
        If fGrid.Rows(rowindex).Cells(1).Value Is Nothing OrElse fGrid.Rows(rowindex).Cells(1).Value.ToString.Contains(".....") OrElse Not fGrid.Rows(rowindex).Cells(1).Value.ToString.Contains("Pinging") Then
            fGrid.Rows(rowindex).Cells(1).Value = "Pinging 10.216.110." & (11 + rowindex).ToString & " "
        Else
            fGrid.Rows(rowindex).Cells(1).Value = fGrid.Rows(rowindex).Cells(1).Value.ToString & "."
        End If
        fGrid.Rows(rowindex).Cells(1).Style.BackColor = Drawing.Color.White
    End Sub
 
    Private success As New List(Of Integer)
    Private done As New List(Of Integer)
 
    Private Sub CheckOnline(ByVal rowindex As Integer)
        Dim _ping As New Ping
        Try
            Dim _pingreply = _ping.Send("10.216.110." & (11 + rowindex).ToString, 2000)
            If _pingreply.Status = IPStatus.Success Then
                SyncLock success
                    success.Add(rowindex)
                End SyncLock
            End If
        Catch ex As Exception
        End Try
        SyncLock done
            done.Add(rowindex)
        End SyncLock
    End Sub
 
    Private Sub SetOnline(ByVal rowindex As Integer)
        If Not success.Contains(rowindex) Then
            fGrid.Rows(rowindex).Cells(1).Value = "Offline"
            fGrid.Rows(rowindex).Cells(1).Style.BackColor = Drawing.Color.Red
        Else
            fGrid.Rows(rowindex).Cells(1).Value = "Online"
            fGrid.Rows(rowindex).Cells(1).Style.BackColor = Drawing.Color.Green
        End If
    End Sub
 
    Private Sub PingToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PingToolStripMenuItem.Click
        done = New List(Of Integer)
        success = New List(Of Integer)
        fThread = New Thread(New ThreadStart(AddressOf ThreadProc))
        fThread.IsBackground = True
        fThread.Start()
        fThread2 = New Thread(New ThreadStart(AddressOf ThreadProc2))
        fThread2.IsBackground = True
        fThread2.Start()
    End Sub
    
End Class
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Net.NetworkInformation

Partial Public Class Form1

	Private fThread As Thread
	Private fThread2 As Thread

	Public Delegate Sub AddRowDelegate(ByVal column As Integer)
	Delegate Sub CheckOnlineDelegate(ByVal rowindex As Integer)
	Delegate Sub SetOnlineDelegate(ByVal rowindex As Integer)

	Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
		fGrid.Columns.Add("Ip", "Ip")
		fGrid.Columns.Add("Ping", "Ping")
		fGrid.Columns(0).Width = 100
		fGrid.Columns(1).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
		For i As Integer = 0 To 4
			Dim myRowIndex = fGrid.Rows.Add()
			fGrid.Rows(myRowIndex).Cells(0).Value = "10.216.110." & (11 + myRowIndex).ToString
		Next
	End Sub

	Private Sub ThreadProc()
		Parallel.For(0, 5, Sub(b)
							   Do While Not done.Contains(b)
								   fGrid.Invoke(New AddRowDelegate(AddressOf AddRow), New Object() {b})
								   Thread.Sleep(300)
							   Loop
							   fGrid.Invoke(New SetOnlineDelegate(AddressOf SetOnline), New Object() {b})
						   End Sub)
	End Sub

	Private Sub ThreadProc2()
		Parallel.For(0, 5, Sub(b)
							   CheckOnline(b)
						   End Sub)
	End Sub

	
	Private Sub AddRow(ByVal rowindex As Integer)
		If fGrid.Rows(rowindex).Cells(1).Value Is Nothing OrElse fGrid.Rows(rowindex).Cells(1).Value.ToString.Contains(".....") OrElse Not fGrid.Rows(rowindex).Cells(1).Value.ToString.Contains("Pinging") Then
			fGrid.Rows(rowindex).Cells(1).Value = "Pinging 10.216.110." & (11 + rowindex).ToString & " "
		Else
			fGrid.Rows(rowindex).Cells(1).Value = fGrid.Rows(rowindex).Cells(1).Value.ToString & "."
		End If
		fGrid.Rows(rowindex).Cells(1).Style.BackColor = Drawing.Color.White
	End Sub

	Private success As New List(Of Integer)
	Private done As New List(Of Integer)

	Private Sub CheckOnline(ByVal rowindex As Integer)
		Dim _ping As New Ping
		Try
			Dim _pingreply = _ping.Send("10.216.110." & (11 + rowindex).ToString, 2000)
			If _pingreply.Status = IPStatus.Success Then
				SyncLock success
					success.Add(rowindex)
				End SyncLock
			End If
		Catch ex As Exception
		End Try
		SyncLock done
			done.Add(rowindex)
		End SyncLock
	End Sub

	Private Sub SetOnline(ByVal rowindex As Integer)
		If Not success.Contains(rowindex) Then
			fGrid.Rows(rowindex).Cells(1).Value = "Offline"
			fGrid.Rows(rowindex).Cells(1).Style.BackColor = Drawing.Color.Red
		Else
			fGrid.Rows(rowindex).Cells(1).Value = "Online"
			fGrid.Rows(rowindex).Cells(1).Style.BackColor = Drawing.Color.Green
		End If
	End Sub

	Private Sub PingToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PingToolStripMenuItem.Click
		done = New List(Of Integer)
		success = New List(Of Integer)
		fThread = New Thread(New ThreadStart(AddressOf ThreadProc))
		fThread.IsBackground = True
		fThread.Start()
		fThread2 = New Thread(New ThreadStart(AddressOf ThreadProc2))
		fThread2.IsBackground = True
		fThread2.Start()
	End Sub
	
End Class

As you can see I invoke the SetOnline and AddRow methods via the grid so that it can be handled in a thread safe way. I also use delegates for that.

While running it you will see that if one is online it will turn green immediately or very fast.

Look also that the Parallel For second parameter is Exclusive To so it does not translate into For i as integer = 0 to 5 but it translates into For i as integer = 0 to 4.

And that you have to wait for about 2 (timeout we used) seconds for all the others to turn red.

Writing thread safe code is tricky but I think I covered all the bases to make this as thread safe as possible without getting race conditions.

Any questions?