Results 1 to 7 of 7

Thread: Threading in VB

  1. #1

    Thread Starter
    Member
    Join Date
    Dec 2010
    Posts
    32

    Threading in VB

    So I am a relative noob at VB programming. I have mostly done Single form stuff, mostly for instrument control. Well, I am working on another form for instrument control, and I was to poll the equipment on an interval. I am using a timer event to poll, but I want to start it in a new thread so it does not affect the main form. I can do the polling, but I want to update items in the main form, but since the polling occurs in a deferent thread I have a cross-threat situation. I am trying to understand how to delegation works, but I am getting confused on what exactly the code has to look like. I did some googling, but most of the examples are not related to what I want to do. Can someone get me pointed in the right direction?

    The information I want to update is several text boxes and checkboxes.
    Last edited by Frozen001; Dec 5th, 2017 at 03:37 PM.

  2. #2
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,989

    Re: Threading in VB

    One thing you might consider is the BackgroundWorker component. It sounds like you are on the right track, in general, as the polling of an instrument is a fairly ideal thing to do on a different thread, and the issue is often about getting the data back to the UI thread. The BGW was added to make that easier in certain situations, and this seems likely to be one. What is particularly convenient about the BGW is that you can raise the Report Progress event, which is raised on the UI thread, so you don't have to worry about the cross threading stuff if you just set that in the progress event handler.

    Having said that, I'd be leaning towards using an actual Thread object for this, though I have no specific reason to say that. Somebody else recently wrote a really nice explanation of invoking, so I figured I'd toss out the BGW as something to look into, and somebody else will come along and write a good invoke explanation.
    My usual boring signature: Nothing

  3. #3
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Threading in VB

    This is a small enough topic I can actually cover it, a rare thing for "a beginner's threading post"!

    First: you don't really need to start a whole new thread. There are 3 timers in .NET, 2 of them use threads, and 2 of them are designed to work with Windows Forms. But, for reasons, we tend to ignore the one that "uses a thread" and "is designed to work with Windows Forms". The two most interesting ones are "I only work on the UI thread" and "I only work on a worker thread".

    The one you are used to is System.Windows.Forms.Timer. You drag it onto a Form, handle its Tick event, and that's about all there is to it. But since it all happens on the UI thread, obviously the form can't update while you're doing things in response to the Tick.

    The one you use if you don't want to occupy the UI thread is System.Threading.Timer. It has a little bit of a clunky interface, but it uses Delegates so you already know you need to know something about it.

    A "delegate" is just a fancy word for "a variable that can represent a Sub or Function". The neat thing about a Delegate is you can call it just like a Sub or Function. (The fancy word for 'Sub or Function' is "Method", and I will use that from now on.)

    You can create a delegate type with a syntax that uses the Delegate keyword, though we don't tend to use this a lot in modern VB. For completion's sake, this is what one might look like:
    Code:
    Public Delegate Sub DoSomethingDelegate()
    This says, "Create a type that can represent any Sub that takes zero parameters." Here's a more complicated one:
    Code:
    Public Delegate Function DoSomethingToNumbersDelegate(ByVal x As Integer, ByVal y As Integer) As Integer
    That's, "Create a type that can represent any Function that takes two Integer parameters and returns an Integer."

    But having a type isn't very useful if you can't do something with it. If you want to create a variable using a Delegate type, you have to tell VB which method it's going to refer to. You also need to use the "AddressOf" keyword to tell VB you want to "use the method as a delegate" instead of "call the method", due to historical syntax reasons. Easier demonstrated than said:
    Code:
    Public Delegate Sub DoSomethingDelegate() 
    
    Public Sub Demo()
        Dim greeter As DoSomethingDelegate = AddressOf SayHello
    
        greeter()
    End Sub
    
    Public Sub SayHello()
        MessageBox.Show("Hi!")
    End Sub
    That probably looks pretty stupid, because it's such a simple example it'd be easier to just do the "right" thing. Let's just keep moving.

    Every control has exactly two things that you can do safely from any thread.

    InvokeRequired is a property that will return True if you are on the "wrong" thread for that control. Invoke() is a method that will take any Delegate you give it and call that delegate on the right thread. See where that's going?

    So when we are working with a Form that knows there will be some things done from other threads, we want to write a "safe" method for doing that work. The pattern goes something like this:
    Code:
    * If I am on the right thread:
        * Do the thing I'm supposed to do.
    * If I am not on the right thread:
        * Create a delegate variable representing this method.
        * Use Invoke() to call that delegate on the correct thread.
    Let's say we're updating a TextBox. Here's the stuff you would write to pull it off:
    Code:
    Public Delegate Sub UpdateTextDelegate(ByVal box As TextBox, ByVal newText As String)
    
    Public Sub UpdateText(ByVal box As TextBox, ByVal newText As String)
        If box.InvokeRequired Then
            box.Invoke(AddressOf(UpdateText), box, newText)
        Else
            box.Text = newText
        End If
    End Sub
    There are cleaner ways to write this. .NET has special delegates already defined for a lot of Sub/Function cases. The one for Subs is called "Action", and it takes generic parameters for each parameter it has. That might be confusing if you're new, but it lets you not have to define a delegate the "long way". If you're curious how generics work... that's another tutorial. But it makes the code look like:
    Code:
    Public Sub UpdateText(ByVal box As TextBox, ByVal newText As String)
        If box.InvokeRequired Then
            Dim toInvoke As New Action(Of TextBox, String)(AddressOf UpdateText)
            box.Invoke(toInvoke, box, newText)
        Else
            box.Text = newText
        End If
    End Sub
    That's not really a lot simpler. Modern VB has an interesting concept called "anonymous methods" or "lambda methods" and a lot of syntax sugar that helps us use them instead of delegates. If you look at a professional's code today you're more likely to see:
    Code:
    Public Sub UpdateText(ByVal box As TextBox, ByVal newText As String)
        If box.InvokeRequired Then
            box.Invoke(Sub() UpdateText(box, newText))
        Else
            box.Text = newText
        End If
    End Sub
    That's the cleanest way to do it, but you have to be real comfy with delegates to understand what's going on. Look for tutorials about lambda methods if you're curious about that, but for the most part you can parrot the syntax and get by.

    That will bring us back to System.Threading.Timer. One of the quirkier things about it is it doesn't raise an event. As usual, it's "for historical reasons". At the time it was created, I think the MS philosophy was, "Events are a thing that we use in Windows Forms, we don't use them in things not intended for that". Part of the reason they might decide something like that is events are fancy delegates, so at the time they felt like "let's do fancy for the GUI and not-fancy for everything else".

    So this type expects you to give it a Delegate. Not just any Delegate will do, it wants something that matches a pre-defined delegate called "TimerCallback". It looks like:
    Code:
    Public Delegate Sub TimerCallback(ByVal state as Object)
    So in order to use this timer, we need to make a Sub that takes an Object parameter. The purpose of that parameter is so you can have the same handler for multiple timers, but that's an advance case so we won't use it. It's not optional, though, so we have to define it. Your basic skeleton to use this timer looks something like this:
    Code:
    Private _interval As TimeSpan = TimeSpan.FromSeconds(5)
    Private _timer As System.Threading.Timer
    
    Public Sub StartTimer()
        _timer = New System.Threading.Timer(AddressOf TimerTick, _interval, _interval)
    End Sub
    
    Public Sub StopTimer()
        _timer.Change(-1, -1)
    End Sub
    
    Private Sub TimerTick(ByVal unused As Object)
        ' ... do stuff
    End Sub
    When you create the timer, you have to give it a "due time" and a "period". This is part of why I consider it clunky. "Due time" represents "how long, from now, until the first tick happens". The "period" is "how long is between each tick". Normally the way we want to use a timer means just setting both to the same thing, but you can do neat things with it. Setting either to -1 means "never", so that's a good way to stop it. (There's a tiny problem with this code that will be addressed in the next example.)

    If you're inside a form, and want to update a control, you need to add your method that safely updates the control. So if you have a random TextBox in the form, we can put everything together:
    Code:
    Private _interval As TimeSpan = TimeSpan.FromSeconds(5)
    Private _timer As System.Threading.Timer
    
    Private _count As Integer = 0
    
    Public Sub StartTimer()
        If _timer = Nothing Then
            _timer = New System.Threading.Timer(AddressOf TimerTick, _interval, _interval)
        Else 
            _timer.Change(_interval, _interval)
        End If
    End Sub
    
    Public Sub StopTimer()
        _timer.Change(-1, -1)
    End Sub
    
    Private Sub TimerTick(ByVal unused As Object)
        _count += 1
        UpdateText(TextBox1, _count.ToString()
    End Sub
    
    Public Sub UpdateText(ByVal box As TextBox, ByVal newText As String)
        If box.InvokeRequired Then
            box.Invoke(Sub() UpdateText(box, newText))
        Else
            box.Text = newText
        End If
    End Sub
    The "tiny change" I made is to not create a new timer every time you call StartTimer(), it's silly to do so. We can just turn it back on by calling Change().

    There's a lot more to think about after this, but they are all tweaks to this setup that you make based on specific needs. The BackgroundWorker component can be a good substitute for this, I kind of got stuck on the idea of a Timer.

    Generally speaking, "I'm going to use the Thread class" is the last tool you think about reaching for. There are a lot of other solutions that are better if you can use them. That's a whole different discussion.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  4. #4
    Angel of Code Niya's Avatar
    Join Date
    Nov 2011
    Posts
    8,598

    Re: Threading in VB

    My signature has a link to an article I wrote on multi-threading a couple years ago. I do explain a bit about delegates and such. You can read that if you want. It may help to clarify things a bit.
    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

  5. #5
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Threading in VB

    You may well have all you need already but you may also like to follow the CodeBank link in my signature below and check out my thread on Accessing Controls From Worker Threads. It specifically provides a set of steps you can follow to write a method that you can call on any thread to update a control(s) on the UI thread. If you follow those steps then it should always work, whether you understand why or not. The resultant code will be a bit more verbose than is strictly necessary but it's clearer and more reusable as a result.

    Also, you may or may not be aware but the Framework includes more than one Timer class and, if you're not already, you should probably be using a System.Timers.Timer in this case rather than a System.Windows.Forms.Timer. The WinForms Timer will always raise its Tick event on the UI thread while the Timers.Timer will raise its Elapsed event on a ThreadPool thread by default or the UI thread if you set the SynchronizingObject property. That way, you can just handle the Elapsed event and you're already on a secondary thread. Note that the Timers.Timer actually wraps the Threading.Timer that Sitten Spynne used above and provides a simpler (or at least more familiar) interface.

  6. #6
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Threading in VB

    That's sort of why I said "no one uses that one".

    The point of using a threaded timer tends to be "My work takes a while and I don't want to do it on the UI thread". So if you use a threaded timer to raise its Tick event on the UI thread, you've only accomplished "the same thing with more steps". Now, if you kick off an asynchronous process from that handler, it's OK, but then you have to ask, "Wait, why am I using the threaded timer again?" The only scenario that comes to mind is "I don't always want it to marshal" but that seems like a dangerous idea. Maybe it's for "My event handling takes longer than the tick" but then you're just overwhelming the thread pool and should be using a mechanism with backpressure.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  7. #7

    Thread Starter
    Member
    Join Date
    Dec 2010
    Posts
    32

    Re: Threading in VB

    Wow... Thanks for the information, I am going to see if I can digest this and get things working the way I want to. I am sure I will be back with more questions, and maybe code snips

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