Solved

Update listbox from another thread

Posted on 2016-08-18
29
92 Views
Last Modified: 2016-09-04
I am trying to update a listbox from another thread, but I never see ..invokerequired return 'true' and then then when it updates the control, it appears to be loading a new instance of the form.

Public Delegate Function DelListEvent(ByVal Msg As String) As Integer

    Public Function ListEvent(ByVal Msg As String) As Integer

        Dim iRet As Integer = 0
        Dim sDS As String = ""

        If lstEvents.InvokeRequired Then
            'iRet = lstEvents.Invoke(New MethodInvoker(AddressOf ListEvent))
            Dim d As New DelListEvent(AddressOf ListEvent)

            'lstEvents.BeginInvoke(New DelListEvent(AddressOf ListEvent), Msg)
            lstEvents.BeginInvoke(d, New Object() {Msg})
        Else
            sDS = Now.ToString("yy/MM/dd HH:mm:ss.fff")

            Me.lstEvents.Items.Add(sDS & " > " & Msg)
            Application.DoEvents()
        End If

    End Function

Open in new window

0
Comment
Question by:sidwelle
  • 17
  • 10
  • 2
29 Comments
 
LVL 32

Expert Comment

by:it_saige
Comment Utility
Invoke required would only be called if the list was in fact updated from a separate thread; e.g. -

Form1.vb -
Imports System.Threading.Tasks

Public Class Form1
	Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			If btn.Equals(Button1) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
										  AddItem(ListBox1, [Item])
									  Next
								  End Sub)
			ElseIf btn.Equals(Button2) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
										  RemoveItem(ListBox1, [Item])
									  Next
								  End Sub)
			End If
		End If
	End Sub

	Delegate Sub AddItemDelegate(lb As ListBox, message As String)
	Delegate Sub RemoveItemDelegate(lb As ListBox, item As Object)

	Sub AddItem(lb As ListBox, message As String)
		If lb.InvokeRequired Then
			Console.WriteLine("Invoking the addition of {0}", message)
			lb.Invoke(New AddItemDelegate(AddressOf AddItem), New Object() {lb, message})
		Else
			Console.WriteLine("Adding {0}; to Listbox", message)
			lb.Items.Add(message)
		End If
	End Sub

	Sub RemoveItem(lb As ListBox, item As Object)
		If lb.InvokeRequired Then
			Console.WriteLine("Invoking the removal of {0}", item)
			lb.Invoke(New RemoveItemDelegate(AddressOf RemoveItem), New Object() {lb, item})
		Else
			Console.WriteLine("Removing {0}; from Listbox", item)
			lb.Items.Remove(item)
		End If
	End Sub
End Class

Open in new window

Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
    Inherits System.Windows.Forms.Form

    'Form overrides dispose to clean up the component list.
    <System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
		Me.ListBox1 = New System.Windows.Forms.ListBox()
		Me.Button1 = New System.Windows.Forms.Button()
		Me.Button2 = New System.Windows.Forms.Button()
		Me.SuspendLayout()
		'
		'ListBox1
		'
		Me.ListBox1.FormattingEnabled = True
		Me.ListBox1.Location = New System.Drawing.Point(13, 13)
		Me.ListBox1.Name = "ListBox1"
		Me.ListBox1.Size = New System.Drawing.Size(259, 212)
		Me.ListBox1.TabIndex = 0
		'
		'Button1
		'
		Me.Button1.Location = New System.Drawing.Point(96, 226)
		Me.Button1.Name = "Button1"
		Me.Button1.Size = New System.Drawing.Size(95, 23)
		Me.Button1.TabIndex = 1
		Me.Button1.Text = "Add 20 Items"
		Me.Button1.UseVisualStyleBackColor = True
		'
		'Button2
		'
		Me.Button2.Location = New System.Drawing.Point(197, 226)
		Me.Button2.Name = "Button2"
		Me.Button2.Size = New System.Drawing.Size(75, 23)
		Me.Button2.TabIndex = 2
		Me.Button2.Text = "Clear"
		Me.Button2.UseVisualStyleBackColor = True
		'
		'Form1
		'
		Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
		Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
		Me.ClientSize = New System.Drawing.Size(284, 261)
		Me.Controls.Add(Me.Button2)
		Me.Controls.Add(Me.Button1)
		Me.Controls.Add(Me.ListBox1)
		Me.Name = "Form1"
		Me.Text = "Form1"
		Me.ResumeLayout(False)

	End Sub
	Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
	Friend WithEvents Button1 As System.Windows.Forms.Button
	Friend WithEvents Button2 As System.Windows.Forms.Button

End Class

Open in new window

Produces the following output -

Initial load:Capture.JPGPressing the add button (with output to console window):Capture.JPGPressing the remove button (with output to console window):Capture.JPG
You could also just use an anonymous method invoker instead of the delegates:
Imports System.Threading.Tasks

Public Class Form1
	Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			If btn.Equals(Button1) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
										  AddItem(ListBox1, [Item])
									  Next
								  End Sub)
			ElseIf btn.Equals(Button2) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
										  RemoveItem(ListBox1, [Item])
									  Next
								  End Sub)
			End If
		End If
	End Sub

	Sub AddItem(lb As ListBox, message As String)
		If lb.InvokeRequired Then
			Console.WriteLine("Invoking the addition of {0}", message)
			lb.Invoke(New MethodInvoker(Sub()
									   AddItem(lb, message)
								   End Sub))
		Else
			Console.WriteLine("Adding {0}; to Listbox", message)
			lb.Items.Add(message)
		End If
	End Sub

	Sub RemoveItem(lb As ListBox, item As Object)
		If lb.InvokeRequired Then
			Console.WriteLine("Invoking the removal of {0}", item)
			lb.Invoke(New MethodInvoker(Sub()
									   RemoveItem(lb, item)
								   End Sub))
		Else
			Console.WriteLine("Removing {0}; from Listbox", item)
							 lb.Items.Remove(item)
		End If
	End Sub
End Class

Open in new window

Which would produce the same output as above.

However, there is a problem with these methods.  They are only for ListBox controls and only for specific listbox control methods.  What if we could make this more generic?  Well we can:
Imports System.Threading.Tasks
Imports System.ComponentModel
Imports System.Runtime.CompilerServices

Public Class Form1
	Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			If btn.Equals(Button1) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
										  ListBox1.HandleInvokeRequired(Sub(lb) lb.Items.Add([Item]))
									  Next
								  End Sub)
			ElseIf btn.Equals(Button2) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
										  ListBox1.HandleInvokeRequired(Sub(lb) lb.Items.Remove([Item]))
									  Next
								  End Sub)
			End If
		End If
	End Sub
End Class

Module Extensions
	<Extension()> _
	Public Sub HandleInvokeRequired(Of T As {Control, ISynchronizeInvoke})(control As T, action As Action(Of T))
		'Check to see is the control is not null
		If control Is Nothing Then
			Throw New ArgumentNullException(String.Format("Cannot execute {0} on {1}.  {1} is null.", action, control))
		End If

		'Check to see if the control is disposed.
		If TypeOf control Is Control AndAlso TryCast(control, Control).IsDisposed Then
			Throw New ObjectDisposedException(String.Format("Cannot execute {0} on {1}.  {1} is disposed.", action, control))
		End If

		'Check to see if the handle is created for the control.
		If TypeOf control Is Control AndAlso Not TryCast(control, Control).IsHandleCreated Then
			Throw New InvalidOperationException(String.Format("Cannot execute {0} on {1}.  Handle is not created for {1}.", action, control))
		End If

		'Check to see if the control's InvokeRequired property is true
		If control.InvokeRequired Then
			Try
				'Use Invoke() to invoke your action
				Console.WriteLine("Invoking the action {0}; on {1}", action, control)
				control.Invoke(action, New Object() {control})
			Catch ex As Exception
				Throw New Exception(String.Format("Cannot execute {0} on {1}.  {2}.", action, control, ex.Message))
			End Try
		Else
			Try
				'Perform the action
				Console.WriteLine("Performing the action {0}; on {1}", action, control)
				action(control)
			Catch ex As Exception
				Throw New Exception(String.Format("Cannot execute {0} on {1}.  {2}.", action, control, ex.Message))
			End Try
		End If
	End Sub
End Module

Open in new window

Which produces a different output to the console but the same results to the listbox -Capture.JPGCapture.JPG
In most circumstances this is fine, but you have the potential for race conditions.

-saige-
0
 

Author Comment

by:sidwelle
Comment Utility
its going to take me a day to read all that out.

Thank you for the reply, but I don't see how line 27 from your first code block in different from my line 13 ?  I am still 'Invoking' the listbox and passing a value.

When I step through it line by line, I see it go through from_load like its loading a new instance of the form that you can't see ?!  
Then it goes through the ListEvent Procedure, the Invokerequired is false and it adds the item to the listbox w/o errors.

But there are no new items visible in the box !
Did it create a new instance of the form that not show ?

Thanks
0
 
LVL 32

Expert Comment

by:it_saige
Comment Utility
The first difference that I see is that your delegate and method do not have the control as a parameter.  Let's attack this from the opposite path, where are you calling your method from?

-saige-
0
 

Author Comment

by:sidwelle
Comment Utility
Why, the control is on the form.  Can't I just call the control by its name w/the form name ?

Me.lstEvents.Items.Add...   'what's wrong with this ?
0
 

Author Comment

by:sidwelle
Comment Utility
I am calling the procedure on the form from a function a thread that I started.

Tried passing a reference to the from in via the constructor of the class, but that didn't work either.

when I hover over the reference, I see a property :
"AccessibilityObject = {"Cross-thread operation not valid: Control 'frmListener' accessed from a thread other than the thread it was created on."}"
0
 
LVL 1

Expert Comment

by:MCSI Developers
Comment Utility
You need to tell the system to not check for cross thread as you state your proble.
"AccessibilityObject = {"Cross-thread operation not valid: Control 'frmListener' accessed from a thread other than the thread it was created on."}"
Try to add this at your form_load as well as at your function at the very top of your function and see.
Control.CheckForIllegalCrossThreadCalls = False

Open in new window

0
 

Author Comment

by:sidwelle
Comment Utility
I added the above line to "form_shown".

Also, instead of passing a reference to the parent form, I passed a reference to the listbox and was able to add items to it directly.

But not sure if this is correct, why can I not call the function "ListEvents(...)" and have it perform the task for me ?
0
 
LVL 1

Expert Comment

by:MCSI Developers
Comment Utility
Why did you do this?
Public Delegate Function DelListEvent(ByVal Msg As String) As Integer

    Public Function ListEvent(ByVal Msg As String) As Integer

Open in new window

0
 

Author Comment

by:sidwelle
Comment Utility
I used a delegate to make the call thread safe ?
0
 
LVL 32

Expert Comment

by:it_saige
Comment Utility
You *can* use your method.  I showed this in my examples.  There are also many articles discussing using delegates to perform tasks on controls; It is not an uncommon request.

As I cannot reproduce your issue directly, why don't you put together a really simple example that shows the issue you are having.

-saige-
0
 

Author Comment

by:sidwelle
Comment Utility
My issue is that I never see the control update the item that I added, when its added from the other thread. Tried using a delegate, tried background worker.

The only way I can get it work is to pass a reference to the thread and then use that reference to update the control. Very much like what you did by passing the "LB".

I have never seen the controls "InvokeRequired" property return 'true'
But the control updates.  Is this thread safe ?
0
 

Author Comment

by:sidwelle
Comment Utility
Put it all back and included the way that you did it, don't know if it will ever use the delegate ?

Works now, but I don't understand why I have to pass the reference all around ?
0
 
LVL 32

Expert Comment

by:it_saige
Comment Utility
If your control updates, then you are not trying to update from a different thread.  If you do not Invoke from a different thread you will get a Cross-Thread exception thrown.

Proof of concept -

We know my code above works because the example clearly shows where invoke is being called via the messages that are written to the console.  What happens if I don't use the delegate:

Form1.vb -
Imports System.Threading.Tasks
Imports System.ComponentModel
Imports System.Runtime.CompilerServices

Public Class Form1
	Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			If btn.Equals(Button1) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
										  ListBox1.Items.Add([Item])
									  Next
								  End Sub)
			ElseIf btn.Equals(Button2) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
										  ListBox1.Items.Remove([Item])
									  Next
								  End Sub)
			End If
		End If
	End Sub
End Class

Open in new window

Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
    Inherits System.Windows.Forms.Form

    'Form overrides dispose to clean up the component list.
    <System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
		Me.ListBox1 = New System.Windows.Forms.ListBox()
		Me.Button1 = New System.Windows.Forms.Button()
		Me.Button2 = New System.Windows.Forms.Button()
		Me.SuspendLayout()
		'
		'ListBox1
		'
		Me.ListBox1.FormattingEnabled = True
		Me.ListBox1.Location = New System.Drawing.Point(13, 13)
		Me.ListBox1.Name = "ListBox1"
		Me.ListBox1.Size = New System.Drawing.Size(259, 212)
		Me.ListBox1.TabIndex = 0
		'
		'Button1
		'
		Me.Button1.Location = New System.Drawing.Point(96, 226)
		Me.Button1.Name = "Button1"
		Me.Button1.Size = New System.Drawing.Size(95, 23)
		Me.Button1.TabIndex = 1
		Me.Button1.Text = "Add 20 Items"
		Me.Button1.UseVisualStyleBackColor = True
		'
		'Button2
		'
		Me.Button2.Location = New System.Drawing.Point(197, 226)
		Me.Button2.Name = "Button2"
		Me.Button2.Size = New System.Drawing.Size(75, 23)
		Me.Button2.TabIndex = 2
		Me.Button2.Text = "Clear"
		Me.Button2.UseVisualStyleBackColor = True
		'
		'Form1
		'
		Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
		Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
		Me.ClientSize = New System.Drawing.Size(284, 261)
		Me.Controls.Add(Me.Button2)
		Me.Controls.Add(Me.Button1)
		Me.Controls.Add(Me.ListBox1)
		Me.Name = "Form1"
		Me.Text = "Form1"
		Me.ResumeLayout(False)

	End Sub
	Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
	Friend WithEvents Button1 As System.Windows.Forms.Button
	Friend WithEvents Button2 As System.Windows.Forms.Button

End Class

Open in new window

InvalidOperationException caused by an invalid Cross-thread operation -Capture.JPG
Let's correct this:

Form1.vb -
Imports System.Threading.Tasks
Imports System.ComponentModel
Imports System.Runtime.CompilerServices

Public Class Form1
	Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			If btn.Equals(Button1) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
										  If ListBox1.InvokeRequired Then
											  Console.WriteLine("Invoking the additon of {0}", [Item])
											  ListBox1.Invoke(New MethodInvoker(Sub()
																			 Console.WriteLine("Adding {0}; to ListBox", [Item])
																			 ListBox1.Items.Add([Item])
																		 End Sub))
										  Else
											  Console.WriteLine("Adding {0}; to ListBox", [Item])
											  ListBox1.Items.Add([Item])
										  End If
									  Next
								  End Sub)
			ElseIf btn.Equals(Button2) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
										  If ListBox1.InvokeRequired Then
											  Console.WriteLine("Invoking the removal of {0}", [Item])
											  ListBox1.Invoke(New MethodInvoker(Sub()
																			 Console.WriteLine("Removing {0}; from ListBox", [Item])
																			 ListBox1.Items.Remove([Item])
																		 End Sub))
										  Else
											  Console.WriteLine("Removing {0}; from ListBox", [Item])
											  ListBox1.Items.Remove([Item])
										  End If
									  Next
								  End Sub)
			End If
		End If
	End Sub
End Class

Module Extensions
	<Extension()> _
	Public Sub HandleInvokeRequired(Of T As {Control, ISynchronizeInvoke})(control As T, action As Action(Of T))
		'Check to see is the control is not null
		If control Is Nothing Then
			Throw New ArgumentNullException(String.Format("Cannot execute {0} on {1}.  {1} is null.", action, control))
		End If

		'Check to see if the control is disposed.
		If TypeOf control Is Control AndAlso TryCast(control, Control).IsDisposed Then
			Throw New ObjectDisposedException(String.Format("Cannot execute {0} on {1}.  {1} is disposed.", action, control))
		End If

		'Check to see if the handle is created for the control.
		If TypeOf control Is Control AndAlso Not TryCast(control, Control).IsHandleCreated Then
			Throw New InvalidOperationException(String.Format("Cannot execute {0} on {1}.  Handle is not created for {1}.", action, control))
		End If

		'Check to see if the control's InvokeRequired property is true
		If control.InvokeRequired Then
			Try
				'Use Invoke() to invoke your action
				Console.WriteLine("Invoking the action {0}; on {1}", action, control)
				control.Invoke(action, New Object() {control})
			Catch ex As Exception
				Throw New Exception(String.Format("Cannot execute {0} on {1}.  {2}.", action, control, ex.Message))
			End Try
		Else
			Try
				'Perform the action
				Console.WriteLine("Performing the action {0}; on {1}", action, control)
				action(control)
			Catch ex As Exception
				Throw New Exception(String.Format("Cannot execute {0} on {1}.  {2}.", action, control, ex.Message))
			End Try
		End If
	End Sub
End Module

Open in new window


Now gives us the expected output (notice without even using a delegate).

-saige-
0
 

Author Comment

by:sidwelle
Comment Utility
it_saige:  Do you recommend the following statement recommended my MCI on this question:

Control.CheckForIllegalCrossThreadCalls = False

Also, I am using an older version of VB.Net 8. It does not recognize "Imports System.Threading.Tasks"
Do I need a new version ?  it this what I am fighting ?
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 32

Expert Comment

by:it_saige
Comment Utility
No I don't recommend using the recommendation by MCI.  You are not solving the issue, you are simply stopping the exception from being thrown.  The whole purpose of the exception is to tell you that something is wrong; in this case the problem, in a nutshell, is that two (or more) threads are being allowed to access and update a control at the same time.  This is a problem because now you have no guarantee as to the state of the control.

The thread that created the control may think that the control is disabled while any of your additional threads believe that the control is enabled; Or worse, the thread that created the handle for the control closes thereby disposing of the control handle, now one of your additional threads try to access, whoops Object disposed exception (or vice versa).  In these cases, you are experiencing concurrency discrepancies caused by race conditions.

To demonstrate race conditions let's take our original application and add a race button to it -

Form1.vb -
Imports System.Threading.Tasks
Imports System.ComponentModel

Public Class Form1
	Private Sub OnClick(sender As Object, e As EventArgs) Handles Button3.Click, Button2.Click, Button1.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			If btn.Equals(Button1) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
										  If ListBox1.InvokeRequired Then
											  Console.WriteLine("Invoking the additon of {0}", [Item])
											  ListBox1.Invoke(New MethodInvoker(Sub()
																			 Console.WriteLine("Adding {0}; to ListBox", [Item])
																			 ListBox1.Items.Add([Item])
																		 End Sub))
										  Else
											  Console.WriteLine("Adding {0}; to ListBox", [Item])
											  ListBox1.Items.Add([Item])
										  End If
									  Next
								  End Sub)
			ElseIf btn.Equals(Button2) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
										  If ListBox1.InvokeRequired Then
											  Console.WriteLine("Invoking the removal of {0}", [Item])
											  ListBox1.Invoke(New MethodInvoker(Sub()
																			 Console.WriteLine("Removing {0}; from ListBox", [Item])
																			 ListBox1.Items.Remove([Item])
																		 End Sub))
										  Else
											  Console.WriteLine("Removing {0}; from ListBox", [Item])
											  ListBox1.Items.Remove([Item])
										  End If
									  Next
								  End Sub)
			ElseIf btn.Equals(Button3) Then
				Dim updaters As New List(Of Task)
				For i = 0 To 10
					Dim j As Integer = i
					updaters.Add(Task.Factory.StartNew(Sub() UpdateButtons(j + 1)))
				Next

				Task.WaitAll(updaters.ToArray())
				Console.WriteLine("Main Thread:  Button1: {0}; Button2: {1}", If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
			End If
		End If
	End Sub

	Sub UpdateButtons(i As Integer)
		Button1.Enabled = Not Button1.Enabled
		Button2.Enabled = Not Button2.Enabled
		Console.WriteLine("Thread {0}: Button1: {1}; Button2: {2}", i, If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
	End Sub

	Private Sub OnLoad(sender As Object, e As EventArgs) Handles MyBase.Load
		Button1.Enabled = False
		Button2.Enabled = False
		Console.WriteLine("Main Thread:  Button1: {0}; Button2: {1}", If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
		CheckForIllegalCrossThreadCalls = False
	End Sub
End Class

Open in new window

Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
    Inherits System.Windows.Forms.Form

    'Form overrides dispose to clean up the component list.
    <System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
		Me.ListBox1 = New System.Windows.Forms.ListBox()
		Me.Button1 = New System.Windows.Forms.Button()
		Me.Button2 = New System.Windows.Forms.Button()
		Me.Button3 = New System.Windows.Forms.Button()
		Me.SuspendLayout()
		'
		'ListBox1
		'
		Me.ListBox1.FormattingEnabled = True
		Me.ListBox1.Location = New System.Drawing.Point(13, 13)
		Me.ListBox1.Name = "ListBox1"
		Me.ListBox1.Size = New System.Drawing.Size(259, 212)
		Me.ListBox1.TabIndex = 0
		'
		'Button1
		'
		Me.Button1.Location = New System.Drawing.Point(96, 226)
		Me.Button1.Name = "Button1"
		Me.Button1.Size = New System.Drawing.Size(95, 23)
		Me.Button1.TabIndex = 1
		Me.Button1.Text = "Add 20 Items"
		Me.Button1.UseVisualStyleBackColor = True
		'
		'Button2
		'
		Me.Button2.Location = New System.Drawing.Point(197, 226)
		Me.Button2.Name = "Button2"
		Me.Button2.Size = New System.Drawing.Size(75, 23)
		Me.Button2.TabIndex = 2
		Me.Button2.Text = "Clear"
		Me.Button2.UseVisualStyleBackColor = True
		'
		'Button3
		'
		Me.Button3.Location = New System.Drawing.Point(12, 226)
		Me.Button3.Name = "Button3"
		Me.Button3.Size = New System.Drawing.Size(75, 23)
		Me.Button3.TabIndex = 3
		Me.Button3.Text = "Race"
		Me.Button3.UseVisualStyleBackColor = True
		'
		'Form1
		'
		Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
		Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
		Me.ClientSize = New System.Drawing.Size(284, 261)
		Me.Controls.Add(Me.Button3)
		Me.Controls.Add(Me.Button2)
		Me.Controls.Add(Me.Button1)
		Me.Controls.Add(Me.ListBox1)
		Me.Name = "Form1"
		Me.Text = "Form1"
		Me.ResumeLayout(False)

	End Sub
	Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
	Friend WithEvents Button1 As System.Windows.Forms.Button
	Friend WithEvents Button2 As System.Windows.Forms.Button
	Friend WithEvents Button3 As System.Windows.Forms.Button
End Class

Open in new window

The expectation is that when we click the race button that both the add and clear buttons are enabled.  The results, however, are hardly the expectation:
First run -Capture.JPGSecond run -Capture.JPGThird run -Capture.JPGAs you can see on two of the runs we got the expected results, but one run gave us unexpected results.  Also take note of the thread ordering (not that it matters for our situation because 11 state changes should ultimately result in each button being enabled from an initial disabled state).

What about with using the Invoke method, does this correct our inconsistent results?  Let's find out:

Form1.vb -
Imports System.Threading.Tasks
Imports System.ComponentModel

Public Class Form1
	Private Sub OnClick(sender As Object, e As EventArgs) Handles Button3.Click, Button2.Click, Button1.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			If btn.Equals(Button1) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
										  If ListBox1.InvokeRequired Then
											  Console.WriteLine("Invoking the additon of {0}", [Item])
											  ListBox1.Invoke(New MethodInvoker(Sub()
																			 Console.WriteLine("Adding {0}; to ListBox", [Item])
																			 ListBox1.Items.Add([Item])
																		 End Sub))
										  Else
											  Console.WriteLine("Adding {0}; to ListBox", [Item])
											  ListBox1.Items.Add([Item])
										  End If
									  Next
								  End Sub)
			ElseIf btn.Equals(Button2) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
										  If ListBox1.InvokeRequired Then
											  Console.WriteLine("Invoking the removal of {0}", [Item])
											  ListBox1.Invoke(New MethodInvoker(Sub()
																			 Console.WriteLine("Removing {0}; from ListBox", [Item])
																			 ListBox1.Items.Remove([Item])
																		 End Sub))
										  Else
											  Console.WriteLine("Removing {0}; from ListBox", [Item])
											  ListBox1.Items.Remove([Item])
										  End If
									  Next
								  End Sub)
			ElseIf btn.Equals(Button3) Then
				Dim updaters As New List(Of Task)
				For i = 0 To 10
					Dim j As Integer = i
					updaters.Add(Task.Factory.StartNew(Sub() UpdateButtons(j + 1)))
				Next

				Task.WaitAll(updaters.ToArray())
				Console.WriteLine("Main Thread:  Button1: {0}; Button2: {1}", If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
			End If
		End If
	End Sub

	Sub UpdateButtons(i As Integer)
		If Button1.InvokeRequired Then
			Button1.BeginInvoke(New MethodInvoker(Sub() Button1.Enabled = Not Button1.Enabled))
		Else
			Button1.Enabled = Not Button1.Enabled
		End If

		If Button2.InvokeRequired Then
			Button2.BeginInvoke(New MethodInvoker(Sub() Button2.Enabled = Not Button2.Enabled))
		Else
			Button2.Enabled = Not Button2.Enabled
		End If
		Console.WriteLine("Thread {0}: Button1: {1}; Button2: {2}", i, If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
	End Sub

	Private Sub OnLoad(sender As Object, e As EventArgs) Handles MyBase.Load
		Button1.Enabled = False
		Button2.Enabled = False
		Console.WriteLine("Main Thread:  Button1: {0}; Button2: {1}", If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
	End Sub
End Class

Open in new window

First run -Capture.JPGSecond run -Capture.JPGThird run -Capture.JPGConsistency is obtained.

As for the second part of your question, your assumption is correct in that System.Threading.Tasks was not made available until .NET 4.0.  VB8 (released as Visual Basic 2005) does not support .NET 4.0.  It does, however, support .NET 2.0 and .NET 3.0 (with an update if I remember correctly).

That being said, the following example should work for you in VS 2005/VB 2005 -

Form1.vb -
Imports System.Threading

Public Class Form1
	Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			If btn.Equals(Button1) Then
				Dim lbUpdater = New Thread(AddressOf AddItems)
				lbUpdater.IsBackground = True
				lbUpdater.Start()
			ElseIf btn.Equals(Button2) Then
				Dim lbUpdator = New Thread(AddressOf RemoveItems)
				lbUpdator.IsBackground = True
				lbUpdator.Start()
			End If
		End If
	End Sub

	Delegate Sub AddItemDelegate(lb As ListBox, message As String)
	Delegate Sub RemoveItemDelegate(lb As ListBox, item As Object)

	Sub AddItems()
		For i As Integer = ListBox1.Items.Count To ListBox1.Items.Count + 20
			AddItem(ListBox1, String.Format("Message{0}", i))
		Next
	End Sub

	Sub RemoveItems()
		For i As Integer = ListBox1.Items.Count - 1 To 0 Step -1
			RemoveItem(ListBox1, ListBox1.Items(i))
		Next
	End Sub

	Sub AddItem(lb As ListBox, message As String)
		If lb.InvokeRequired Then
			Console.WriteLine("Invoking the addition of {0}", message)
			lb.Invoke(New AddItemDelegate(AddressOf AddItem), New Object() {lb, message})
		Else
			Console.WriteLine("Adding {0}; to Listbox", message)
			lb.Items.Add(message)
		End If
	End Sub

	Sub RemoveItem(lb As ListBox, item As Object)
		If lb.InvokeRequired Then
			Console.WriteLine("Invoking the removal of {0}", item)
			lb.Invoke(New RemoveItemDelegate(AddressOf RemoveItem), New Object() {lb, item})
		Else
			Console.WriteLine("Removing {0}; from Listbox", item)
			lb.Items.Remove(item)
		End If
	End Sub
End Class

Open in new window

Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
	Inherits System.Windows.Forms.Form

	'Form overrides dispose to clean up the component list.
	<System.Diagnostics.DebuggerNonUserCode()> _
	Protected Overrides Sub Dispose(ByVal disposing As Boolean)
		Try
			If disposing AndAlso components IsNot Nothing Then
				components.Dispose()
			End If
		Finally
			MyBase.Dispose(disposing)
		End Try
	End Sub

	'Required by the Windows Form Designer
	Private components As System.ComponentModel.IContainer

	'NOTE: The following procedure is required by the Windows Form Designer
	'It can be modified using the Windows Form Designer.  
	'Do not modify it using the code editor.
	<System.Diagnostics.DebuggerStepThrough()> _
	Private Sub InitializeComponent()
		Me.ListBox1 = New System.Windows.Forms.ListBox()
		Me.Button1 = New System.Windows.Forms.Button()
		Me.Button2 = New System.Windows.Forms.Button()
		Me.SuspendLayout()
		'
		'ListBox1
		'
		Me.ListBox1.FormattingEnabled = True
		Me.ListBox1.Location = New System.Drawing.Point(13, 13)
		Me.ListBox1.Name = "ListBox1"
		Me.ListBox1.Size = New System.Drawing.Size(259, 212)
		Me.ListBox1.TabIndex = 0
		'
		'Button1
		'
		Me.Button1.Location = New System.Drawing.Point(96, 226)
		Me.Button1.Name = "Button1"
		Me.Button1.Size = New System.Drawing.Size(95, 23)
		Me.Button1.TabIndex = 1
		Me.Button1.Text = "Add 20 Items"
		Me.Button1.UseVisualStyleBackColor = True
		'
		'Button2
		'
		Me.Button2.Location = New System.Drawing.Point(197, 226)
		Me.Button2.Name = "Button2"
		Me.Button2.Size = New System.Drawing.Size(75, 23)
		Me.Button2.TabIndex = 2
		Me.Button2.Text = "Clear"
		Me.Button2.UseVisualStyleBackColor = True
		'
		'Form1
		'
		Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
		Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
		Me.ClientSize = New System.Drawing.Size(284, 261)
		Me.Controls.Add(Me.Button2)
		Me.Controls.Add(Me.Button1)
		Me.Controls.Add(Me.ListBox1)
		Me.Name = "Form1"
		Me.Text = "Form1"
		Me.ResumeLayout(False)

	End Sub
	Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
	Friend WithEvents Button1 As System.Windows.Forms.Button
	Friend WithEvents Button2 As System.Windows.Forms.Button

End Class

Open in new window

-saige-
0
 

Author Comment

by:sidwelle
Comment Utility
Your example is easy to follow, but I can't reproduce it because I never got beyond:
"Imports System.Threading.Tasks"  

"Does not contain a public member and cannot be found"

Do I need to use another version of VB other than 8 ?
0
 
LVL 32

Expert Comment

by:it_saige
Comment Utility
Yes, in order to use the code dependent upon System.Threading.Tasks, you need VB 10 (Visual Studio 2010) or higher, targeting .NET 4.0 or higher.  The most current version, VS 2015, is available in a community edition for free if you qualify.

https://www.visualstudio.com/products/visual-studio-community-vs

-saige-
0
 

Author Comment

by:sidwelle
Comment Utility
Can I get it done with using vb 8 using .Net 3.5 Using Delegates ?
That's currently what we have.
0
 
LVL 32

Expert Comment

by:it_saige
Comment Utility
It may have been lost in the message, but I did include a version that should work with VB 8 in my response here: https:/Q_28964240.html?anchor=a41765714#a41765639

Reposting just the compatible portion of the code:

Form1.vb -
Imports System.Threading

Public Class Form1
	Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			If btn.Equals(Button1) Then
				Dim lbUpdater = New Thread(AddressOf AddItems)
				lbUpdater.IsBackground = True
				lbUpdater.Start()
			ElseIf btn.Equals(Button2) Then
				Dim lbUpdator = New Thread(AddressOf RemoveItems)
				lbUpdator.IsBackground = True
				lbUpdator.Start()
			End If
		End If
	End Sub

	Delegate Sub AddItemDelegate(lb As ListBox, message As String)
	Delegate Sub RemoveItemDelegate(lb As ListBox, item As Object)

	Sub AddItems()
		For i As Integer = ListBox1.Items.Count To ListBox1.Items.Count + 20
			AddItem(ListBox1, String.Format("Message{0}", i))
		Next
	End Sub

	Sub RemoveItems()
		For i As Integer = ListBox1.Items.Count - 1 To 0 Step -1
			RemoveItem(ListBox1, ListBox1.Items(i))
		Next
	End Sub

	Sub AddItem(lb As ListBox, message As String)
		If lb.InvokeRequired Then
			Console.WriteLine("Invoking the addition of {0}", message)
			lb.Invoke(New AddItemDelegate(AddressOf AddItem), New Object() {lb, message})
		Else
			Console.WriteLine("Adding {0}; to Listbox", message)
			lb.Items.Add(message)
		End If
	End Sub

	Sub RemoveItem(lb As ListBox, item As Object)
		If lb.InvokeRequired Then
			Console.WriteLine("Invoking the removal of {0}", item)
			lb.Invoke(New RemoveItemDelegate(AddressOf RemoveItem), New Object() {lb, item})
		Else
			Console.WriteLine("Removing {0}; from Listbox", item)
			lb.Items.Remove(item)
		End If
	End Sub
End Class

Open in new window


Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
	Inherits System.Windows.Forms.Form

	'Form overrides dispose to clean up the component list.
	<System.Diagnostics.DebuggerNonUserCode()> _
	Protected Overrides Sub Dispose(ByVal disposing As Boolean)
		Try
			If disposing AndAlso components IsNot Nothing Then
				components.Dispose()
			End If
		Finally
			MyBase.Dispose(disposing)
		End Try
	End Sub

	'Required by the Windows Form Designer
	Private components As System.ComponentModel.IContainer

	'NOTE: The following procedure is required by the Windows Form Designer
	'It can be modified using the Windows Form Designer.  
	'Do not modify it using the code editor.
	<System.Diagnostics.DebuggerStepThrough()> _
	Private Sub InitializeComponent()
		Me.ListBox1 = New System.Windows.Forms.ListBox()
		Me.Button1 = New System.Windows.Forms.Button()
		Me.Button2 = New System.Windows.Forms.Button()
		Me.SuspendLayout()
		'
		'ListBox1
		'
		Me.ListBox1.FormattingEnabled = True
		Me.ListBox1.Location = New System.Drawing.Point(13, 13)
		Me.ListBox1.Name = "ListBox1"
		Me.ListBox1.Size = New System.Drawing.Size(259, 212)
		Me.ListBox1.TabIndex = 0
		'
		'Button1
		'
		Me.Button1.Location = New System.Drawing.Point(96, 226)
		Me.Button1.Name = "Button1"
		Me.Button1.Size = New System.Drawing.Size(95, 23)
		Me.Button1.TabIndex = 1
		Me.Button1.Text = "Add 20 Items"
		Me.Button1.UseVisualStyleBackColor = True
		'
		'Button2
		'
		Me.Button2.Location = New System.Drawing.Point(197, 226)
		Me.Button2.Name = "Button2"
		Me.Button2.Size = New System.Drawing.Size(75, 23)
		Me.Button2.TabIndex = 2
		Me.Button2.Text = "Clear"
		Me.Button2.UseVisualStyleBackColor = True
		'
		'Form1
		'
		Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
		Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
		Me.ClientSize = New System.Drawing.Size(284, 261)
		Me.Controls.Add(Me.Button2)
		Me.Controls.Add(Me.Button1)
		Me.Controls.Add(Me.ListBox1)
		Me.Name = "Form1"
		Me.Text = "Form1"
		Me.ResumeLayout(False)

	End Sub
	Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
	Friend WithEvents Button1 As System.Windows.Forms.Button
	Friend WithEvents Button2 As System.Windows.Forms.Button

End Class

Open in new window


-saige-
0
 

Author Comment

by:sidwelle
Comment Utility
Ok, found issue, changed:

      Sub AddItem(lb As ListBox, message As String)
            If lstBox.InvokeRequired Then    'My Line


      Sub AddItem(lb As ListBox, message As String)
            If lb.InvokeRequired Then   'To your line

This shows me how to update the controls, Now I need to get to vars on form module ?
0
 
LVL 32

Expert Comment

by:it_saige
Comment Utility
In this case lb is the ListBox, but if you need form instance specific access, you should be able to use the FindForm method to find the form that the control is bound to; e.g. -
Imports System.Threading

Public Class Form1
	Private SomeText As String = "SomeText"

	Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			If btn.Equals(Button1) Then
				Dim lbUpdater = New Thread(AddressOf AddItems)
				lbUpdater.IsBackground = True
				lbUpdater.Start()
			ElseIf btn.Equals(Button2) Then
				Dim lbUpdator = New Thread(AddressOf RemoveItems)
				lbUpdator.IsBackground = True
				lbUpdator.Start()
			End If
		End If
	End Sub

	Delegate Sub AddItemDelegate(lb As ListBox, message As String)
	Delegate Sub RemoveItemDelegate(lb As ListBox, item As Object)

	Sub AddItems()
		For i As Integer = ListBox1.Items.Count To ListBox1.Items.Count + 20
			AddItem(ListBox1, String.Format("Message{0}", i))
		Next
	End Sub

	Sub RemoveItems()
		For i As Integer = ListBox1.Items.Count - 1 To 0 Step -1
			RemoveItem(ListBox1, ListBox1.Items(i))
		Next
	End Sub

	Sub AddItem(lb As ListBox, message As String)
		If lb.InvokeRequired Then
			Console.WriteLine("Invoking the addition of {0}", message)
			lb.Invoke(New AddItemDelegate(AddressOf AddItem), New Object() {lb, message})
		Else
			Console.WriteLine("Adding {0}; to Listbox", message)
			Dim instance = lb.FindForm()
			Console.WriteLine(CType(instance, Form1).SomeText)
			lb.Items.Add(message)
		End If
	End Sub

	Sub RemoveItem(lb As ListBox, item As Object)
		If lb.InvokeRequired Then
			Console.WriteLine("Invoking the removal of {0}", item)
			lb.Invoke(New RemoveItemDelegate(AddressOf RemoveItem), New Object() {lb, item})
		Else
			Console.WriteLine("Removing {0}; from Listbox", item)
			lb.Items.Remove(item)
		End If
	End Sub
End Class

Open in new window


-saige-
0
 

Author Comment

by:sidwelle
Comment Utility
it_saige, still working on this project. its just a little busy here.

I plan on posting a mock of project demoing the issue in a day or two.

Thank you.
0
 
LVL 32

Expert Comment

by:it_saige
Comment Utility
Thanks for the update.  No worries, been busy on this side of the internet as well.  ;)

-saige-
0
 

Author Comment

by:sidwelle
Comment Utility
Here is a mock up of the project.
For the most part it works.
Issues:
   1.      You have to pass a reference to the listbox (or control you want to update) to your new threads.
   2.      If you try to broadcast a banner on the new connection, the app becomes un-stable. (event just the first one!?)
   3.      when I test the app from a VB Classic app, I always get double/repeated text strings arriving at the client ?
      -> Can't reproduce this from a telnet client.



'*** form Code ***
Public Class frmTest01

    Private Sub cmdStart_Click(sender As System.Object, e As System.EventArgs) Handles cmdStart.Click

        bListening01 = True
        StartListening01(Me.lstEvents)	'Call the listening function, and pass a reference to the listbox "lstEvents"

    End Sub

    Public Function ListEvent00(ByVal Msg As String) As Integer
        ListEvent01(Me.lstEvents, Msg)

    End Function

    Public Delegate Function DelListEvent(ByVal LB As ListBox, ByVal Msg As String) As Integer

    Public Function ListEvent01(ByVal msg As String) As Integer

        ListEvent01(lstEvents, msg)

    End Function

    Public Function ListEvent01(ByVal LB As ListBox, ByVal Msg As String) As Integer

        Dim iRet As Integer = 0
        Dim sDS As String = ""
        Dim sMsg As String = ""
        Dim sMsg_p As String = ""
        Dim iPos As Integer = -1

        'If lstEvents.InvokeRequired Then
        If LB.InvokeRequired Then
            'iRet = lstEvents.Invoke(New MethodInvoker(AddressOf ListEvent))
            Dim d As New DelListEvent(AddressOf ListEvent01)

            'lstEvents.BeginInvoke(New DelListEvent(AddressOf ListEvent), Msg)
            LB.BeginInvoke(d, New Object() {LB, Msg})
        Else
            sDS = Now.ToString("yy/MM/dd HH:mm:ss.fff")
            sMsg = sDS & " > " & Msg

            iPos = sMsg.IndexOf("~")
            If iPos > -1 Then
                Do Until iPos = -1
                    sMsg_p = sMsg.Substring(0, iPos)
                    LB.Items.Add(sMsg_p)

                    sMsg = sMsg.Substring(iPos + 1)
                    iPos = sMsg.IndexOf("~")
                Loop

                LB.Items.Add(sMsg)
            Else
                LB.Items.Add(sMsg)
            End If

            'Select the last item.
            LB.SelectedIndex = LB.Items.Count - 1

            Do Until LB.Items.Count < 11
                LB.Items.RemoveAt(0)
                Application.DoEvents()
            Loop

            Application.DoEvents()
        End If

    End Function

    Private Sub frmTest01_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        bListening01 = False
    End Sub

End Class

Open in new window

'*** Module Code ***
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
'Imports System.Threading.tasks
Imports System.ComponentModel

Module modTesting01

    Public sBanner As String = " Your are connected to the TCP Listener !" & vbCrLf
    Public iThreadCount01 As Integer = 0
    Public iConnectionCnt01 As Integer = 0
    Public dServiceUpTime As Date = Now
    Public dStartedListening01 As Date
    Public bSendBanner As Boolean = True
    Public bListening01 As Boolean = False
    Public sTargetFolder As String = "C:\Projects\TestFiles\"
    Public iCounter As Integer = 0
    Public bLogDataRec01 As Boolean = False

    Sub StartListening01(ByVal lsb As ListBox)
        On Error Resume Next

        Dim iPort As Integer = 9100

	'Here its ok not to pass a reference to the listbox, we are not in a separate thread.
        frmTest01.ListEvent01("Listener Started on Port: " & iPort.ToString)

        sTargetFolder = frmListener.txtTargetFolder.Text
        Dim LclIP As IPAddress = IPAddress.Parse("127.0.0.1")
        Dim serverSocket As New TcpListener(LclIP, iPort)
        Dim clientSocket As TcpClient

        serverSocket.Start()
        iCounter = 0
        While (bListening01)

            If serverSocket.Pending Then
                iCounter += 1
                clientSocket = serverSocket.AcceptTcpClient()
                Application.DoEvents()
                Dim client As New handleClinet01

		'Start the thread and pass a reference to the listbox "lstEvents"
                client.startClient(clientSocket, lsb, iCounter)
                frmTest01.ListEvent01(lsb, "Connection accepted from " & clientSocket.Client.RemoteEndPoint.ToString & "  #" & iCounter.ToString("#,0"))

                'frmTest01.ListEvent01(lsb, "Process Thread Count: " & Process.GetCurrentProcess().Threads.Count.ToString("#,0"))

            End If
            Threading.Thread.Sleep(100)
            Application.DoEvents()

        End While

        clientSocket.Close()
        serverSocket.Stop()

        frmTest01.ListEvent01("Listener Stopped.")

    End Sub

    Sub msg(ByVal mesg As String)
        mesg.Trim()
        Console.WriteLine(" >> " + mesg)
    End Sub

    Public Class handleClinet01
        Dim clientSocket As TcpClient
        Dim frmLsb As ListBox
        Dim iCounter As Integer = 0

        Public Sub startClient(ByVal inClientSocket As TcpClient, ByVal lsb As ListBox, Counter As Integer)
            Me.clientSocket = inClientSocket
            Me.frmLsb = lsb
            Dim ctThread As Threading.Thread = New Threading.Thread(AddressOf Capture)
            ctThread.Priority = ThreadPriority.Highest
            ctThread.Start(lsb)	'and pass a reference to the listbox "lstEvents"
            iCounter = Counter

            ctThread = Nothing
            MyClass.Finalize()

        End Sub

        Private Sub Capture(ByVal lsb As ListBox)

            Dim requestCount As Integer = 0
            Dim bytesEmpty(10024) As Byte
            Dim bytesFrom(10024) As Byte
            Dim sDataFromClient As String = ""
            Dim sLineFromClient As String = ""
            Dim sendBytes As [Byte]()
            Dim serverResponse As String = ""
            Dim rCount As String = ""
            Dim iRead As Integer = -1
            Dim sFileName As String = Now.ToString("yyMMdd_HHmmss_fff") & ".txt"
            Dim sTargetFolder As String = "C:\Projects\TestFiles\"

            Dim sPath As String = sTargetFolder & sFileName
            Dim bBannerSent As Boolean = False
            Dim sRemoteAddr As String = clientSocket.Client.RemoteEndPoint.ToString
            Dim iByteCount As Integer = 0
            Dim iThread0 As Integer = 0

            Dim networkStream As NetworkStream = clientSocket.GetStream()

            'Writing anything back to the Socket causes the app to crash ??
            'If iCounter < 4 Then        'Only try to write the first few connections, otherwise the app will crash !
            '    sendBytes = Encoding.ASCII.GetBytes(sBanner)
            '    networkStream.Write(sendBytes, 0, sendBytes.Length)
            '    networkStream.Flush()
            'End If

            While (bListening01)
                Try
                    requestCount = requestCount + 1

                    'bytesFrom = bytesEmpty
                    iRead = networkStream.Read(bytesFrom, 0, CInt(clientSocket.ReceiveBufferSize))
                    'networkStream.Flush()
                    sDataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom)
                    sDataFromClient = sDataFromClient.Substring(0, sDataFromClient.IndexOf(Chr(0)))

                    iByteCount += sDataFromClient.Length

                    sLineFromClient &= sDataFromClient
                    sDataFromClient = ""

                    If sLineFromClient.IndexOf(vbCr) > -1 Then
                        IO.File.AppendAllText(sPath, sLineFromClient)
                        sLineFromClient = ""
                    End If

                    If iRead = 0 Then   'We are disconnected.

			'Now the remote connection has closed, pass a reference to the listbox "lstEvents" or you will never get your form updated.
                        frmTest01.ListEvent01(frmLsb, "Connection closed from !   " & sRemoteAddr)

                        If sLineFromClient.Length > 0 Then

                            IO.File.AppendAllText(sPath, sLineFromClient)
                            sLineFromClient = ""
                        End If

                        Exit While
                    End If

                Catch ex As Exception
                    'MsgBox(ex.ToString)
                End Try

            End While


	    'Close everything up.
            networkStream.Dispose()
            Me.clientSocket.Close()

            Me.clientSocket = Nothing
            'Thread.CurrentThread.Abort()
            Application.DoEvents()

            'Beep()
        End Sub

    End Class

End Module

Open in new window

0
 

Author Comment

by:sidwelle
Comment Utility
Look at this link:
http://www.codeproject.com/Articles/10533/When-a-thread-will-terminate-or-dispose

That makes this comment:
Remember: You don't have to declare a delegate for the procedure you want to use for threading in VB.NET, it will be done behind the scenes in contrast to C#.
0
 
LVL 32

Accepted Solution

by:
it_saige earned 500 total points
Comment Utility
First, why do you think you do not have to pass a reference to the object you want to update?  How are you supposed to know what to update if you do not tell the runtime which object you want to update?  This is why the common signature for events is:
Sub EventName(sender as Object, e As EventArgs)

Open in new window

The sender *is* the control that *invoked* the event.  The purpose of invocation is to ensure that only one thread updates the control.  That is it.  The thread tasked with the job of updating the control is the thread that created the handle to the control.

Delegates are just references to methods.  By doing this, you can send a method as a parameter to another method.  A simple example to show you what I mean:
Module FunWithDelegates
    Delegate Function FilterDelegate(ByVal p As Person) As Boolean

    Sub Main()
        Dim people As Person() = {New Person("John", 41), New Person("Jane", 69), New Person("Jake", 12), New Person("Jessie", 25)}
        DisplayPeople("Children:", people, AddressOf IsChild)
        DisplayPeople("Adults:", people, AddressOf IsAdult)
        DisplayPeople("Seniors:", people, AddressOf IsSenior)
        Console.ReadLine()
    End Sub

    Sub DisplayPeople(ByVal Title As String, ByVal People As Person(), ByVal Filter As FilterDelegate)
        Console.WriteLine(Title)

        For Each p As Person In People
            If (Filter(p)) Then
                Console.WriteLine("{0}, {1} years old", p.Name, p.Age)
            End If
        Next
        Console.WriteLine(Environment.NewLine)
    End Sub

    Function IsChild(ByVal p As Person) As Boolean
        Return p.Age <= 18
    End Function

    Function IsAdult(ByVal p As Person) As Boolean
        Return p.Age >= 18
    End Function

    Function IsSenior(ByVal p As Person) As Boolean
        Return p.Age >= 65
    End Function
End Module

Class Person
    Private _Name As String
    Private _Age As Integer

    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property
    Public Property Age() As Integer
        Get
            Return _Age
        End Get
        Set(ByVal value As Integer)
            _Age = value
        End Set
    End Property

    Public Sub New(ByVal Name As String, ByVal Age As Integer)
        Me.Name = Name
        Me.Age = Age
    End Sub
End Class

Open in new window

Which produces the following output -Capture.PNG
Now we did not need to use a delegate, we could have just as *easily* (not really?!?) passed the method itself as one of the arguments; e.g. -
Module FunWithoutDelegates
    Interface FilterInterface
        Function Evaluate(ByVal p As Person) As Boolean
    End Interface

    Sub Main()
        Dim people As Person() = {New Person("John", 41), New Person("Jane", 69), New Person("Jake", 12), New Person("Jessie", 25)}
        DisplayPeople("Children:", people, New IsChild())
        DisplayPeople("Adults:", people, New IsAdult())
        DisplayPeople("Seniors:", people, New IsSenior())
        Console.ReadLine()
    End Sub

    Sub DisplayPeople(ByVal Title As String, ByVal People As Person(), ByVal Filter As FilterInterface)
        Console.WriteLine(Title)

        For Each p As Person In People
            If (Filter.Evaluate(p)) Then
                Console.WriteLine("{0}, {1} years old", p.Name, p.Age)
            End If
        Next
        Console.WriteLine(Environment.NewLine)
    End Sub

    Class IsChild
        Implements FilterInterface
        Public Function Evaluate(ByVal p As Person) As Boolean Implements FilterInterface.Evaluate
            Return p.Age <= 18
        End Function
    End Class

    Class IsAdult
        Implements FilterInterface
        Public Function Evaluate(ByVal p As Person) As Boolean Implements FilterInterface.Evaluate
            Return p.Age >= 18
        End Function
    End Class

    Class IsSenior
        Implements FilterInterface
        Public Function Evaluate(ByVal p As Person) As Boolean Implements FilterInterface.Evaluate
            Return p.Age >= 65
        End Function
    End Class
End Module

Class Person
    Private _Name As String
    Private _Age As Integer

    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property
    Public Property Age() As Integer
        Get
            Return _Age
        End Get
        Set(ByVal value As Integer)
            _Age = value
        End Set
    End Property

    Public Sub New(ByVal Name As String, ByVal Age As Integer)
        Me.Name = Name
        Me.Age = Age
    End Sub
End Class

Open in new window

Which produces the exact same output as the delegate method.  Whats the difference?  A smaller code footprint for starters and an easier to follow logic flow (once you recognize the pattern).  Initially, however, the second method does lend itself to following easier because of it's verbosity.  So your link is not lying, you don't need to declare the delegate (which I also illustrated in one of my previous examples: https:/Q_28964240.html?anchor=a41775423#a41761669), you could always just use the method.

As for your example code above, I am missing the following parts, the Designer file for frmTest01 and the frmListener referenced in modTesting01.

-saige-
0
 

Author Comment

by:sidwelle
Comment Utility
I will try to upload those files tonight when I get home.
0
 

Author Comment

by:sidwelle
Comment Utility
The "frmListener" is left over from a previous project. (just comment that line please)

The "frmTest01" is just a form with a button named "cmdStart" and a listbox named "lstEvents".  

I thought I provided everything, let me know if anything is left out.

Thanks
0
 

Author Closing Comment

by:sidwelle
Comment Utility
Thanks for the help, your code examples really helped.
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Many of us here at EE write code. Many of us write exceptional code; just as many of us write exception-prone code. As we all should know, exceptions are a mechanism for handling errors which are typically out of our control. From database errors, t…
Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

728 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

14 Experts available now in Live!

Get 1:1 Help Now