Multithreaded ping sweeping in VB.NET 2005

by Pber July 21, 2009 16:44

Multithreading

Learning multithreading is not one of the easiest things to do.  Microsoft made it much easier to implement with the .NET framework, but it's still not a small undertaking.  One of the challenges I found when first learning multithreading was trying to find good examples.  There are a lot of them out there.  They were either too easy or too hard and complex for the beginner to understand without getting confused by the code.  The easy ones are good for getting the principals down, but didn't demonstrate how to get real data into a thread or data out of a thread.

I wanted to try and develop something middle of the road.  So I decided to make a ping sweep application that could ping a range of IPs and report back the ping result information and place it into a listview.  I also wanted to add options to either select the use of no threading, Free threading, and thread pools.  I will discuss each of these next.  I chose not to include the backgroundworker class in this application as it caused some difficulties integrating into my existing methods and procedures and caused some complexity that I was trying to avoid in the first place.  The demo also takes advantage of the flicker free listview as discussed in my article here.

The ping sweep timings for each of these next methods were done on a class C subnet of 254 hosts with about 80% of the hosts online using a packet size of 32 bytes and a ping time out of 1000ms.

No Threading

This method just runs on the User Interface (UI) thread.  This usually involves a loop  or a long math calculation that update come control.  During its execution, the UI is essentially blocked and the user can't click anything until the processing is complete.  This is why we want to do multithreading.  With multithreading, we can do other things while tasks such as long math calculation, file transfers, or many pings are done in the background.  Using the ping sweeping demo program to ping my test subnet using no threading, it takes around 60 seconds to complete the ping sweep. 

Free Threading

I refer to free threading as just creating as many threads as you need.   In the ping sweeping demo application, using the free threading method will create as many ping threads as you have hosts.  This has an advantage of being extremely fast.  Ping sweeping the same subnet as in the previous example takes about 2 seconds using free threading.  Had it not completed so quickly, you would have noticed the GUI remained responsive.  Nice performance increase, but there is also a downside to this.  You can easily swamp the CPU or memory resources by rapidly spawning so many threads.  Using this method, if you attempted to ping sweep 2500 hosts you will likely deplete your CPU and memory to the point of throwing an out of memory exception.

There is also another downside of spawning so many threads that update the GUI.  In .NET, controls on the UI can only be updated by the thread they were created on.  When you spawn new threads, the only way to update controls on the UI is to marshall the data back to the UI thread via a delegate sub that runs on the UI thread.  So when you spawn lots of threads, you can end up with the UI thread being swamped as too many worker threads are trying to marshall data back all a once.

You could build your own thread control where you keep track of the number of threads you are running and throttle the amount of threads you create, but this could get complex.  So what do we do.  Use a threadpool.

ThreadPools

Threadpools are queues and are used almost the same as free threading.  Instead of creating a thread to point to your function then starting each thread, you just place the pointer to your function in a threadpool.  The threadpool will then take care of processing each thread and control how many threads can run at once.  The advantage of this is you now don't swamp your CPU or memory resources and still take advantage using threading to keep your GUI responsive.  The downside is you will lose a little speed as not as many threads are running at once, however you can also control the amount of threads that a threadpool uses.  By default it uses 25 threads per CPU.  Ping sweeping the same subnet in the previous two examples takes about 8 seconds using a threadpool of 25 threads.

Keeping track of threads

Now that you have a bunch of threads running, how do you keep track of them to know when they are all done.  I chose to use the Interlocked class.  It provided the ability to increment or decrement a threadcount variable in a thread safe way.  So as I spawned each thread I would call the increment method of the interlocked class to increase the threadcount variable.  As each thread completes, I would call the decrement method of the interlocked class  to decrease the threadcount variable.  When the threadcount variable reaches 0, all threads are done. 

Enough talk

Here's my demo ping sweeper written in VB.NET 2.0.  Have a look at the code, I hope I explained it well enough here as well as in the code comments.  Give it a try and let me know what you think.

PingSweeperv1.0_Demo.zip (21.23 kb)

Tags: , ,

Programming

Comments (11) -

9/18/2009 11:47:02 AM #

It still floors me why people give away free tools, let alone the source code.  It's way faster than any other one I've tried.

Thanks, awesome work.

alex United States

9/18/2009 1:44:12 PM #

Glad to help

Pber Canada

4/13/2010 3:39:59 AM #

Thanks for posting this. Very descriptive, and informative. I'm just learning out in .Net after using VB6 for many years, and have found your articles aimed at the right pitch.

Much impressed.

Chris United Kingdom

11/7/2010 9:42:57 AM #

This is an awesome example of threading with real world feedback in a UI.

There are way too many sample snippets out there that don't really help a coder understand or follow how threading works in reality.

This was super informative, thanks for sharing the code.

-Jeff

Jeff LiCausi United States

3/31/2011 7:00:58 AM #


Effecient and effective code, I will use it as start for learning more on visual NET
environmet.
Thanks for sharing this nice code.
-Sandro

Sandro United Kingdom

7/19/2011 6:42:33 PM #

Excellent, awesome. It really helps me with my real world problem. I found one lack and i want to get a suggestion. How to add pause and resume capability? And how do we stop the task without closing the form?

Waiting for your reply.

Shaoun1000 United States

8/10/2011 8:55:06 AM #

Shaoun1000,

This was just a basic demo of multithreading to get the concepts down.  One way to pause execution would be to use the thread.sleep method. There is also thread.abort methods to kill threads.  However these aren't really good method of controlling threads.  

I personally like using the threadpool method, but the threadpool does not have a method to dequeue items or pause the pool.  In this scenario, I would rather re-write the Ping_Freethread routine and control the thread rate with my own code.  You could spawn the thread creation from another thread and just do a thread.sleep(timeout.infinite) and then thread.interupt to resume it.
See this for more info: msdn.microsoft.com/en-us/library/tttdef8x.aspx

Pber United States

8/10/2011 5:39:23 PM #

Hi, you took long time to reply. I was looking everyday for this. You told thread.Pause or Thread.abort. Can you post an example code. Where they will work best?

Thanks.

Shaoun1000 Bangladesh

8/11/2011 8:50:52 AM #

Shaoun1000, Sorry I was off on Vacation.

As I mentioned, I personally prefer using threadpools, but there is no way to control the threads after they have been queued, so we're back to free threading.

The easiest way to do this would be to just modify the Ping_Freethread routine a little to allow it to be interrupted. So basically I just added a global variable called “start” that keeps track of where the progress left off when it was paused. I also added two global Boolean variables to track when the pause/resume and stop buttons were pressed.  

Add the following to Public class form1 class:

Dim bpause As Boolean = False
Dim bstop = False
Dim start As Integer

Add the following to Private Sub cmdPing_Click subroutine:

bstop = False

'non-threadpool
If Me.rThreadFree.Checked Then
    Ping_FreeThread(1) ‘note change here
End If



Replace Ping_FreeThread to this:

Private Sub Ping_FreeThread(ByVal istart As Integer)
    Dim i As Integer
    For i = istart To lv.Items.Count
        If bstop Then
            Exit Sub
        End If
        If bpause = True Then
            start = I ‘save where we were to resume
            Exit Sub
        End If

        'create class
        Dim cping As New classPing
        'setup class for each ping
        cping.index = i
        cping.timeout = CInt(Me.numTimeout.Text)
        cping.size = CInt(Me.numPacketsize.Text)
        cping.host = lv.Items(i - 1).Text
        cping.pingDelegate = AddressOf pingdone

       'track thread count
        Interlocked.Increment(threadcount)
        Try
            'create thread for each ping
            Dim thPing As New Thread(AddressOf cping.ping)
            thPing.IsBackground = True
            thPing.Priority = ThreadPriority.BelowNormal
            thPing.Start()

        Catch ex As Exception
            MsgBox(ex.Message)
        End Try

        'control the threading speed and allow form not to block.
        If threadcount > 10 Then
            Thread.Sleep(10)
            System.Windows.Forms.Application.DoEvents()
        End If
    Next
End Sub


Also add two buttons called button1 and button2.  Change button1’s text to Pause and button2’s text to Stop, then add the following code:

'Pause/Resume button
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    If bpause Then
        Button1.Text = "Pause"
        bpause = False
        Me.Refresh()
        Ping_FreeThread(start)

    Else
        Button1.Text = "Resume"
        bpause = True
        Me.Refresh()
    End If
End Sub

'stop button
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    bstop = True
    Controls_UnLock()
    displaystats()
End Sub

Pber Canada

8/11/2011 12:53:09 PM #

I forgot to mention in the previous post, I only modified the minimum to allow for pause/resume and stop.  The statistics, the status bar and the control locking would need to be adjusted to compensate for the changes.  

If I were to rewrite this, I would not use button1, but change cmdPing to perform the start and pause/resume functions and just add a stop button.

Pber Canada

8/11/2011 6:31:47 PM #

Excellent. Now Stop, Pause and Resume is possible. One more question is there any performance issue?

Thanks for your nice guideline. I used thread pool before but could not control it. That is why i began to work with background worker. But this excellent example is clearing my concept on threading. I also tried Abortable Thread Pool class but none works better than yours.

This blog is a good place for programmers but i see very little articles. Is there any separate place where i can find more of yours?

Thanks.

Shaoun1000 Bangladesh

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading


Powered by BlogEngine.NET 2.0.0.36
Theme by Mads Kristensen | Modified by Pber