Results 1 to 40 of 149

Thread: Understanding Multi-Threading in VB.Net

Threaded View

  1. #2

    Thread Starter
    Angel of Code Niya's Avatar
    Join Date
    Nov 2011
    Posts
    9,017

    Re: Understanding Multi-Threading in VB.Net

    How its done

    We'll start off simple and then build on it. Let us begin with a simple operation. We are going to use a loop that counts from 1 to 100 to simulate a long running operation like a file download.

    Open a new Windows Forms project and place a Button and a Label on Form1. Double click the Button to bring up its Click event handler then code the event handler as:-
    vbnet Code:
    1. '
    2.     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    3.         For i = 1 To 100
    4.             Label1.Text = CStr(i)
    5.             Threading.Thread.Sleep(200)
    6.         Next
    7.     End Sub
    Execute the program and click the button. You should notice that the UI freezes until the count is actually complete. Notice that while its frozen, the label is not being updated. Why is this ? Well remember the message loop I mentioned in the Backround section of this article ?. Pressing the button executes the count loop so while this loop is running the message loop of the UI cannot execute and process the repaint messages for the label to update itself. The UI also cannot process mouse clicks, and key presses. You cannot even move the window.

    Our solution is to pass off the count to another thread to execute so that the UI's message loop remains unhindered. Remember, the UI's message loop is running in its own thread usually called the main thread or the UI thread.

    We'll start off by putting our counting operation into a separate sup:-
    vbnet Code:
    1. '
    2.     Private Sub Count(ByVal Max As Integer)
    3.         For i = 1 To Max
    4.             Label1.Text = CStr(i)
    5.             Threading.Thread.Sleep(200)
    6.         Next
    7.  
    8.     End Sub

    What we do next is really a matter of preference. Some people elect to use the BackgroundWorker component to do their multi-threading. I'm not going to show that approach. The forum is rich with examples on using the BackgroundWorker and I've always preferred to do my threading directly. This is the direction I'm going to demonstrate.

    Now that we have our count in its own sub. Code the Button's Click event as:-
    vbnet Code:
    1. '
    2.     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    3.         Dim t1 As New Threading.Thread(AddressOf Count)
    4.  
    5.         t1.Start(100)
    6.     End Sub
    The first line instantiates a new Thread object and passes it the address of the Count sub since this is the sub we want to execute on another thread. Its very important to note that here lies a very serious limitation. Only a sub with a single parameter is allowed. Luckily in our example Count only has one parameter, the number at which the count should end.

    The second line starts the thread. Start takes one parameter which it uses to call the sub hence:-
    vbnet Code:
    1. t1.Start(100)
    Is like:-
    vbnet Code:
    1. Count(100)
    Only it runs on a different thread from the thread it was called from.

    If you have Option Strict On the add this Count overload:-
    vbnet Code:
    1. '
    2.     Private Sub Count(ByVal Max As Object)
    3.         If TypeOf Max Is Integer Then
    4.             Count(CInt(Max))
    5.         End If
    6.     End Sub
    The reason is because the Thread class's constructor expects the address to a sub that takes one parameter of type object. Option Strict would not allow the address of the Count that takes an Integer as its parameter to be used. Now execute the program.

    At this point you should be ready to pull out your hair because you would get this error:-


    So just what in God's name is happening here ? Don't worry, that error is actually happening to prevent potential disasters. Remember I said that the UI has its own thread ? Well think about it what is the UI ? Its basically every thing to do with what you see. Labels, pictures, text are all UI elements so any control that is responsible for showing these things is a part of the UI. The windows message loop runs in the UI thread and its responsible for dealing with these controls. Its repainting, its responses to key presses and mouse clicks are all the UI's thread is responsible for. Its rather intrusive for another thread to just come out of nowhere and make a change to a control that its not responsible for. How does this foreign thread know what the UI is doing with any give control at any given moment ? It doesn't and if it interferes with a control while the UI was in the middle of making some change to the control then it can really cause some instability in the application if the control ends up in some invalid state. This is why VB.Net prevents us from changing the Label's text from another thread.

    The correct way to go about this is for the foreign thread to politely ask the UI's thread to update the Label for us. If the UI's thread is busy when the foreign thread makes the request, the request is simply queued and when the UI's thread is finish whatever its doing, its message loop will move on to process the other messages in the queue which would eventually lead it to the request to execute the code that would change the Label's text.

    Start by making a function to change the Label's text:-
    vbnet Code:
    1. '
    2.     Private Sub SetLabelText(ByVal text As String)
    3.         If Label1.InvokeRequired Then
    4.             Label1.Invoke(New Action(Of String)(AddressOf SetLabelText), text)
    5.         Else
    6.             Label1.Text = text
    7.  
    8.         End If
    9.     End Sub
    In this sub we call the Label's InvokeRequired property to determine if the Label is being accessed in the current sub from the UI thread or another thread. It returns True if it was called from a foreign thread.

    vbnet Code:
    1. Label1.Invoke(New Action(Of String)(AddressOf SetLabelText), text)
    The above line submits a request to the UI thread to re-run SetLabelText but instead of running it on the foreign thread, it would then execute on UI thread. InvokeRequired would then return False which would execute the Label's text change. This is the key because the UI thread would only execute the sub when its not busy processing some other message.

    All that's left now is to change Count:-
    vbnet Code:
    1. '
    2.     Private Sub Count(ByVal Max As Integer)
    3.         For i = 1 To Max
    4.             SetLabelText(CStr(i))
    5.             Threading.Thread.Sleep(200)
    6.         Next
    7.  
    8.     End Sub

    We use SetLabelText to change the Label's text instead of trying to change it directly and viola! We have successfully created a simple multi-threaded application.

    One thing remains. Execute the program, click the button and close the form before the count is finished. If you did this in the IDE, you would notice that the program doesn't actually stop. That's because the thread is a foreground thread. Usually, we want a background thread for operations like these. Background threads are terminated when the application terminates which is actually when all foreground threads terminate. The UI thread is a foreground thread and by closing the form we terminate that thread but the application won't terminate because the counter is running on another foreground thread. In this case its undesirable so we must make a change:-
    vbnet Code:
    1. '
    2.     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    3.         Dim t1 As New Threading.Thread(AddressOf Count)
    4.  
    5.         t1.IsBackground = True
    6.  
    7.         t1.Start(100)
    8.     End Sub

    We set IsBackground to True when we create the thread in the Button's Click event handler. Now, the application has only one foreground thread so the application ends when this thread terminates which automatically terminates all background threads.

    This is the most basic way to do straight threading in VB.Net. Now there are a couple things I still want to cover like thread pool threads, using threads to create asynchronous methods on classes, and using events to signal when operations on other threads are finished. I'll cover that on a later date in the next post. Until then folks, happy threading !!

    Note: The following attachment is the demo project. It was written using VS2008 so you must have VS2008 and later to open it.
    Attached Files Attached Files
    Last edited by Niya; Jul 3rd, 2012 at 10:55 PM. Reason: Minor grammer fix
    Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading | Simple file compression | Demon Arena

    Copy/move files using Windows Shell | I'm not wanted

    C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter

    There's just no reason to use garbage like InputBox. - jmcilhinney

    The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width