Wednesday, November 28, 2012

Multi thread for newbies

Example of multi thread for newbies in visualbasic


Multithread, multi-thread, multi-tasking…. Anyway all means the same thing. Is a program that could do many things at the same time to make your program faster. With ordinarily program, everything is done sequentially.

If you read this article, is because you want to make your programs faster, you want your program to be able to do 2 things at the same time or simply interested to know how your program could use multi-core processor.

Is very important to say right now the concept is not for beginner at the first place. This is more like for intermediate level. But I will try to give the best for a newbie or for a starter.

You could immediately download the sample if you don’t want any explanation at the end of this article. I will separate this post into 2 sections: regular programming and programming with threading (multi-thread).


.
.

Regular programming.


In early days, programs and computers instructions where send one by one in order. Everything was extremely predictable. Each line code was executed one after another.

[Image tik tak too]
tik tak too

In the sample program included in this article, you will have a form with a big loop that fills a listview. Is not important what the ListView is. Is only important to know that when the loop runs, the program use all the resource available to run the loop. The form will because  inaccessible. If you try to click on the form, you might see (program not responding).

[Image red form]

If you press the button2, the program will fill the listview and it might take 30 seconds before your form become accessible. The bottom left button won’t respond while the loop is running.

Here is a sample of the code for the first program form1 with no threading:

[Code for form1]


Public Class Form1
#Region "members"
    Private WithEvents ListView1 As System.Windows.Forms.ListView

#End Region
    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Init()
        Me.Text = "no Threading"
        Me.BackColor = Color.Red

        Dim oForm2 As New Form2 'this line is not good practice
        oForm2.Text = "with threading"
        oForm2.BackColor = Color.Green
        oForm2.Show()
    End Sub

    Private Sub Init()
        'ListView1 = New System.Windows.Forms.ListView
        Dim index1 As Integer

        ListView1 = New ListView
        With ListView1
            .Location = New System.Drawing.Point(20, 14)
            .Name = "ListView1"
            .Size = New System.Drawing.Size(393, 254)
            .TabIndex = 3
            .UseCompatibleStateImageBehavior = False
            .View = View.Details
            .GridLines = True
            .Scrollable = True
            .MultiSelect = True
            .FullRowSelect = True
        End With
        For index1 = 0 To 100 Step 1
            ListView1.Columns.Add("Title: " & index1.ToString)
        Next

        With Button1
            .Text = "change title"
        End With

        Me.Controls.Add(listView1)
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Me.Text = Me.Text & " hello you!"
    End Sub

    Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
        Dim lvi As ListViewItem
        Dim index1 As Integer
        Dim index2 As Integer
        Dim str() As String
        Dim rdn As New Random
        ReDim str(ListView1.Columns.Count)

        For index1 = 0 To 1000 Step 1
            For index2 = 0 To ListView1.Columns.Count Step 1
                str(index2) = rdn.Next & rdn.Next & rdn.Next & rdn.Next
            Next
            lvi = New ListViewItem(str)
            ListView1.Items.Add(lvi)
        Next


    End Sub
End Class



And here is the form layout from the form designer of Visual Studio 2010:

 form layout from the form designer of Visual Studio 2010

In button2.click there is a big loop that steals all the program resources. When the loop ends, others events will work.

.
.

Multi-thread programming.

I take exactly the same programming and simply converted to a multi-thread program. Is very simple that way I present it to you. I did it on purpose. Let me remind you that you could download directly the code sample of this article or try to follow and understand my explanation. Remember that is not easy for newbies, but if you are reading this, is because you show some interest in this subject.


.
.

Step 1 for multi-threading: take all your big code and put it in a new function.


In the form 1, button2 contain a big code. If the user clicks on button2 from form1, it will run a big code for 30 seconds. Now I am asking you to isolate the code. Cut and paste all that big code in a new function or sub. Name it anything you want, is not important. But in my example, I called it: “ListView_Working




    Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click


        'Step 1
        ListView_Working()

           
    End Sub


    ''' <summary>
    ''' will work in backgroud
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub ListView_Working()
        Dim lvi As ListViewItem
        Dim index1 As Integer
        Dim index2 As Integer
        Dim str() As String
        Dim rdn As New Random
        ReDim str(ListView1.Columns.Count)

        For index1 = 0 To 100 Step 1
            For index2 = 0 To ListView1.Columns.Count Step 1
                str(index2) = rdn.Next & rdn.Next & rdn.Next & rdn.Next
            Next
            lvi = New ListViewItem(str)

            ListView1.Items.Add(lvi) ' don't need anymore  (Step 2)
        Next

    End Sub


To end this first step, you need to call your function from the button2 of form2. In other words, you are simply clean your code and putting all the big code elsewhere.

If you run your code now, your code should 100% the same way than before. Again, this is the first step.

Step 1 only for multi thread
.

Step 2: Create the thread

Let me remind you that on step 1, you isolated the code in another function but nothing really changes in your program. All the shit is still the same. I know but you have to do it if you want to survive at this introduction.

Now you have to create the thread. A thread is an object. You could create the thread ANYWHERE you want in your form, is not important because it will work. If you create it in a sub or function, it will work for sure at 100%, but I would not recommend it. Since this is an introduction for newbies, I rather show it the good way. I suggest you to create it in your form but outside any sub or function. In the code from form2, I created a private thread called : ListView_Working_Thread . [xxxxx]


Public Class Form2
#Region "members"

    Private ListView_Working_Thread As Threading.Thread 'Step 2

#End Region

.
.
.
.
.
[…]



It is really not important for new to declare your thread public or private. It is really up to you. The thread is an object like any other object.

Now, if you run your program now, nothing will change. Is the same garbage and it acts identically like form1. I know, I know. But it is important to do this step, because you need the thread. The thread will exist somewhere in your computer and will do as you say in the next step.

I am sorry if my English is not that good. I am trying to use significant tile to describe my ideas in my own comprehension.

Step 2 only for multi thread
.
.

Step 3: merge the thread and the function



Ok. Go in the button2 click event. Notice that there is simply one line : ListView_Working.
That is the name of the function that calls your big code. Now you need to do 1 thing: PUT THE FUNCTION IN THE THREAD.
Is this logic for you??????? You want the big working code running independently from all the rest of your program.

Please observe and try to understand the next code. Compare it with the previous one.

    Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click


        'Step 1
        'ListView_Working()

        'Step 3
        If ListView_Working_Thread Is Nothing Then
            ListView_Working_Thread = New Threading.Thread(AddressOf ListView_Working)
            ListView_Working_Thread.IsBackground = True
            ListView_Working_Thread.Start()
        End If

    
    End Sub


The previous code is a suggestion; you could put more line to improve the quality and more security.

Step 4: Using the Invoke command


Now if you run your code right now, you will have an error right here:


    Private Sub ListView_Working()
        Dim lvi As ListViewItem
        Dim index1 As Integer
        Dim index2 As Integer
        Dim str() As String
        Dim rdn As New Random
        ReDim str(ListView1.Columns.Count)

        For index1 = 0 To 5000 Step 1
            For index2 = 0 To ListView1.Columns.Count Step 1
                str(index2) = rdn.Next & rdn.Next & rdn.Next & rdn.Next
            Next
            lvi = New ListViewItem(str)

            ListView1.Items.Add(lvi) 'Problem here
        Next

    End Sub


System.InvalidOperationException was unhandled
Cross-thread operation not valid: Control 'ListView1' accessed from a thread other than the thread it was created on.


System.InvalidOperationException was unhandled


This error is normal because your thread can’t not access to your “main” program. You separated it from your main program so both functions could run independently, but of course, you need them to work together. Then how could you make all threads works together? Well you can’t. You can’t separate 2 things in parallel and put them in sequence at your convenience. But you could do some small thing. Of course, the more your get good, the more you learn of new tools. Remember this suppose to be for beginners.

Your need to change the regular command line instruction to a invoke. What is invoke? For the moment, we don’t know. We just need to correct the error.

To correct the Invalid Operation you need 2 lines! Yes, only 2 lines.

First, you need to declare a special function using the keyword Delegate. Again, is not important to know what delegate means. Write something like this inside your class but outside your sub and functions.


Public Class Form2
#Region "members"

    Private ListView_Working_Thread As Threading.Thread 'Step One
    Private Delegate Sub ListView_Add_Delegate(ByVal lvi As ListViewItem) 'Step 3


#End Region

.
.
.
[…]


Then comment or delete the line with the error and replace it like for a new line of code using the Invoke function.


'ListView1.Items.Add(lvi) ' don't need anymore  (Step 2)
Me.Invoke(New ListView_Add_Delegate(AddressOf ListView_Add), lvi)


Now if you run the code, you will be able to whatever in the form while the big code that calculates a lot of stuff is working. Never the background program will interrupt the form.

ListView in thread


Your final code would look like this for form2.




''' <summary>
''' This is a sample from Check Technologies ltée
''' http://checktechno.ca
''' </summary>
''' <remarks></remarks>
'''
Public Class Form2
#Region "members"
    Private WithEvents ListView1 As System.Windows.Forms.ListView
    Private ListView_Working_Thread As Threading.Thread 'Step One
    Private Delegate Sub ListView_Add_Delegate(ByVal lvi As ListViewItem) 'Step 3


#End Region
    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Init()
    End Sub

    Private Sub Init()
        'ListView1 = New System.Windows.Forms.ListView
        Dim index1 As Integer

        ListView1 = New System.Windows.Forms.ListView
        With ListView1
            .Location = New System.Drawing.Point(20, 14)
            .Name = "ListView1"
            .Size = New System.Drawing.Size(393, 254)
            .TabIndex = 3
            .UseCompatibleStateImageBehavior = False
            .View = View.Details
            .GridLines = True
            .Scrollable = True
            .MultiSelect = True
            .FullRowSelect = True
        End With
        For index1 = 0 To 100 Step 1
            ListView1.Columns.Add("Title: " & index1.ToString)
        Next

        With Button1
            .Text = "change title"
        End With

        Me.Controls.Add(ListView1)
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Me.Text = Me.Text & " hello you!"
    End Sub

    Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click


        'Step 1
        ListView_Working()

        'Step 2
        If ListView_Working_Thread Is Nothing Then
            ListView_Working_Thread = New Threading.Thread(AddressOf ListView_Working)
            ListView_Working_Thread.IsBackground = True
            ListView_Working_Thread.Start()
        End If


    End Sub

    'this is not enought
    Private Sub ListView_Working_old()
        Dim lvi As ListViewItem
        Dim index1 As Integer
        Dim index2 As Integer
        Dim str() As String
        Dim rdn As New Random
        ReDim str(ListView1.Columns.Count)

        For index1 = 0 To 9000 Step 1
            For index2 = 0 To ListView1.Columns.Count Step 1
                str(index2) = rdn.Next & rdn.Next & rdn.Next & rdn.Next
            Next
            lvi = New ListViewItem(str)
            ListView1.Items.Add(lvi)
        Next

    End Sub

    ''' <summary>
    ''' will work in background
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub ListView_Working()
        Dim lvi As ListViewItem
        Dim index1 As Integer
        Dim index2 As Integer
        Dim str() As String
        Dim rdn As New Random
        ReDim str(ListView1.Columns.Count)

        For index1 = 0 To 5000 Step 1
            For index2 = 0 To ListView1.Columns.Count Step 1
                str(index2) = rdn.Next & rdn.Next & rdn.Next & rdn.Next
            Next
            lvi = New ListViewItem(str)

            'ListView1.Items.Add(lvi) ' don't need anymore  (Step 2)
            Me.Invoke(New ListView_Add_Delegate(AddressOf ListView_Add), lvi) 'step 4
        Next

    End Sub

    ''' <summary>
    ''' this is step 4 : Invoke
    ''' </summary>
    ''' <param name="lvi"></param>
    ''' <remarks></remarks>
    Private Sub ListView_Add(ByVal lvi As ListViewItem)

        ListView1.Items.Add(lvi)

    End Sub

End Class




Step 5: improve performance

This is where my article ends. I will try to write more details.
You will notice that the listview will always call for the refresh or the repaint screen making your program slower. Yes I know is not logic for now. Well you need to do more things to make your program fun to use. But of course, this is an example and everything here is on purpose.

If you like this post, please comment it or share it.


Code Sample : MultiTaskingSample.zip
Check Technologies is Official Web Site : http://checktechno.ca



1 comment:

  1. Put this in Init():

    Dim aProp As System.Reflection.PropertyInfo = GetType(ListView).GetProperty("DoubleBuffered", System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance)

    aProp.SetValue(ListView1, True, Nothing)

    ReplyDelete